diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index ecc8b535..1de5cc8e 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -14,37 +14,65 @@ jobs: test: strategy: matrix: - # os: [macos-latest, ubuntu-latest] - os: [macos-latest] + os: + - macos-latest + - ubuntu-24.04 llvm: [18] - runs-on: ${{ matrix.os }} + runs-on: ${{matrix.os}} steps: - uses: actions/checkout@v4 - - name: Update Homebrew - # needed as long as LLVM 18 is still fresh - if: matrix.llvm == 18 && startsWith(matrix.os, 'macos') - run: brew update - - name: Install LLVM ${{ matrix.llvm }} and bdw-gc + - name: Install dependencies if: startsWith(matrix.os, 'macos') run: | - HOMEBREW_NO_AUTO_UPDATE=1 brew install llvm@${{ matrix.llvm }} bdw-gc - echo `brew --prefix llvm@${{ matrix.llvm }}`/bin >> $GITHUB_PATH - - name: Install LLVM ${{ matrix.llvm }} and libgc-dev + brew update + brew install llvm@${{matrix.llvm}} pkg-config bdw-gc openssl + echo "$(brew --prefix llvm@${{matrix.llvm}})/bin" >> $GITHUB_PATH + + # Install optional deps for demos. + # + # NOTE: Keep this list updated as new deps are introduced. + opt_deps=( + cjson # for github.com/goplus/llgo/c/cjson + sqlite # for github.com/goplus/llgo/c/sqlite + python@3.12 # for github.com/goplus/llgo/py + ) + brew install "${opt_deps[@]}" + + - name: Install dependencies if: startsWith(matrix.os, 'ubuntu') run: | - echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-${{ matrix.llvm }} main" | sudo tee /etc/apt/sources.list.d/llvm.list + echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-${{matrix.llvm}} main" | sudo tee /etc/apt/sources.list.d/llvm.list wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - sudo apt-get update - sudo apt-get install -y llvm-${{ matrix.llvm }}-dev clang-${{ matrix.llvm }} lld-${{ matrix.llvm }} pkg-config libgc-dev libcjson-dev libsqlite3-dev python3.11-dev - echo /usr/lib/llvm-${{ matrix.llvm }}/bin >> $GITHUB_PATH - + sudo apt-get install -y llvm-${{matrix.llvm}}-dev clang-${{matrix.llvm}} lld-${{matrix.llvm}} pkg-config libgc-dev libssl-dev zlib1g-dev + echo "/usr/lib/llvm-${{matrix.llvm}}/bin" >> $GITHUB_PATH + + # Install optional deps for demos. + # + # NOTE: Keep this list updated as new deps are introduced. + opt_deps=( + libcjson-dev # for github.com/goplus/llgo/c/cjson + libsqlite3-dev # for github.com/goplus/llgo/c/sqlite + python3.12-dev # for github.com/goplus/llgo/py + ) + sudo apt-get install -y "${opt_deps[@]}" + + - name: Install further optional dependencies for demos + run: | + wget -P ./_demo/llama2-c https://huggingface.co/karpathy/tinyllamas/resolve/main/stories15M.bin + py_deps=( + numpy # for github.com/goplus/llgo/py/numpy + torch # for github.com/goplus/llgo/py/torch + ) + pip3 install --break-system-packages "${py_deps[@]}" + - name: Clang information run: | echo $PATH which clang clang --version - + - name: Set up Go uses: actions/setup-go@v5 with: @@ -54,28 +82,26 @@ jobs: run: go build -v ./... - name: Test - if: matrix.os != 'macos-latest' + if: ${{!startsWith(matrix.os, 'macos')}} run: go test -v ./... - name: Test with coverage - if: matrix.os == 'macos-latest' + if: startsWith(matrix.os, 'macos') run: go test -v -coverprofile="coverage.txt" -covermode=atomic ./... - name: Install run: go install ./... - + - name: LLGO tests - if: matrix.os != 'ubuntu-latest' + if: ${{!startsWith(matrix.os, 'ubuntu')}} run: | - echo "Test result on ${{ matrix.os }} with LLVM ${{ matrix.llvm }}" > result.md - LLGOROOT=$PWD bash .github/workflows/test_llgo.sh - - - name: Test _demo and _pydemo - run: | - set +e - LLGOROOT=$PWD bash .github/workflows/test_demo.sh - exit 0 - + echo "Test result on ${{matrix.os}} with LLVM ${{matrix.llvm}}" > result.md + bash .github/workflows/test_llgo.sh + + - name: Test demos + continue-on-error: true + run: bash .github/workflows/test_demo.sh + - name: Show test result run: cat result.md @@ -84,10 +110,10 @@ jobs: if: false with: filePath: result.md - comment_tag: test-result-on-${{ matrix.os }}-with-llvm-${{ matrix.llvm }} + comment_tag: test-result-on-${{matrix.os}}-with-llvm-${{matrix.llvm}} - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 with: - token: ${{ secrets.CODECOV_TOKEN }} + token: ${{secrets.CODECOV_TOKEN}} slug: goplus/llgo diff --git a/.github/workflows/test_demo.sh b/.github/workflows/test_demo.sh index c707b00e..8e947f6a 100644 --- a/.github/workflows/test_demo.sh +++ b/.github/workflows/test_demo.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -e # llgo run subdirectories under _demo and _pydemo total=0 @@ -8,7 +9,7 @@ for d in ./_demo/* ./_pydemo/*; do total=$((total+1)) if [ -d "$d" ]; then echo "Testing $d" - if ! llgo run "$d"; then + if ! (cd "$d" && llgo run .); then echo "FAIL" failed=$((failed+1)) failed_cases="$failed_cases\n* :x: $d" diff --git a/.github/workflows/test_llgo.sh b/.github/workflows/test_llgo.sh index 0ecff3a1..a1b97fe3 100644 --- a/.github/workflows/test_llgo.sh +++ b/.github/workflows/test_llgo.sh @@ -1,8 +1,6 @@ #!/bin/bash set -e -export LLGOROOT=$PWD - testcmd=/tmp/test llgo build -o $testcmd ./c/bdwgc/_test cases=$($testcmd) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 9dc465b9..bd4efc18 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -20,8 +20,8 @@ builds: flags: - -tags=darwin,amd64,byollvm ldflags: - - -X github.com/goplus/llgo/xtool/env.buildVersion=v{{.Version}} - - -X github.com/goplus/llgo/xtool/env.buildDate={{.Date}} + - -X github.com/goplus/llgo/x/env.buildVersion=v{{.Version}} + - -X github.com/goplus/llgo/x/env.buildTime={{.Date}} - -X github.com/goplus/llgo/xtool/env/llvm.ldLLVMConfigBin=/usr/local/opt/llvm@18/bin/llvm-config env: - CC=o64-clang @@ -36,8 +36,8 @@ builds: flags: - -tags=darwin,arm64,byollvm ldflags: - - -X github.com/goplus/llgo/xtool/env.buildVersion=v{{.Version}} - - -X github.com/goplus/llgo/xtool/env.buildDate={{.Date}} + - -X github.com/goplus/llgo/x/env.buildVersion=v{{.Version}} + - -X github.com/goplus/llgo/x/env.buildTime={{.Date}} - -X github.com/goplus/llgo/xtool/env/llvm.ldLLVMConfigBin=/opt/homebrew/opt/llvm@18/bin/llvm-config env: - CC=oa64-clang @@ -52,8 +52,8 @@ builds: flags: - -tags=linux,amd64,byollvm ldflags: - - -X github.com/goplus/llgo/xtool/env.buildVersion=v{{.Version}} - - -X github.com/goplus/llgo/xtool/env.buildDate={{.Date}} + - -X github.com/goplus/llgo/x/env.buildVersion=v{{.Version}} + - -X github.com/goplus/llgo/x/env.buildTime={{.Date}} - -X github.com/goplus/llgo/xtool/env/llvm.ldLLVMConfigBin=/usr/lib/llvm-18/bin/llvm-config env: - CC=x86_64-linux-gnu-gcc @@ -68,8 +68,8 @@ builds: flags: - -tags=linux,arm64,byollvm ldflags: - - -X github.com/goplus/llgo/xtool/env.buildVersion=v{{.Version}} - - -X github.com/goplus/llgo/xtool/env.buildDate={{.Date}} + - -X github.com/goplus/llgo/x/env.buildVersion=v{{.Version}} + - -X github.com/goplus/llgo/x/env.buildTime={{.Date}} - -X github.com/goplus/llgo/xtool/env/llvm.ldLLVMConfigBin=/usr/lib/llvm-18/bin/llvm-config env: - CC=aarch64-linux-gnu-gcc diff --git a/README.md b/README.md index 7dff6402..22db1411 100644 --- a/README.md +++ b/README.md @@ -214,9 +214,11 @@ The currently supported libraries include: * [c/bdwgc](https://pkg.go.dev/github.com/goplus/llgo/c/bdwgc) * [c/cjson](https://pkg.go.dev/github.com/goplus/llgo/c/cjson) * [c/clang](https://pkg.go.dev/github.com/goplus/llgo/c/clang) +* [c/libuv](https://pkg.go.dev/github.com/goplus/llgo/c/libuv) * [c/llama2](https://pkg.go.dev/github.com/goplus/llgo/c/llama2) * [c/lua](https://pkg.go.dev/github.com/goplus/llgo/c/lua) * [c/neco](https://pkg.go.dev/github.com/goplus/llgo/c/neco) +* [c/openssl](https://pkg.go.dev/github.com/goplus/llgo/c/openssl) * [c/raylib](https://pkg.go.dev/github.com/goplus/llgo/c/raylib) * [c/sqlite](https://pkg.go.dev/github.com/goplus/llgo/c/sqlite) * [c/zlib](https://pkg.go.dev/github.com/goplus/llgo/c/zlib) @@ -242,7 +244,7 @@ All Go syntax (not including `cgo`) is already supported. Here are some examples * [goroutine](_demo/goroutine/goroutine.go): goroutine demo -## Defer +### Defer LLGo `defer` does not support usage in loops. This is not a bug but a feature, because we think that using `defer` in a loop is a very unrecommended practice. @@ -267,17 +269,24 @@ Here are the Go packages that can be imported correctly: * [unicode/utf8](https://pkg.go.dev/unicode/utf8) * [unicode/utf16](https://pkg.go.dev/unicode/utf16) * [math](https://pkg.go.dev/math) +* [math/big](https://pkg.go.dev/math/big) (partially) * [math/bits](https://pkg.go.dev/math/bits) * [math/cmplx](https://pkg.go.dev/math/cmplx) +* [math/rand](https://pkg.go.dev/math/rand) * [errors](https://pkg.go.dev/errors) * [context](https://pkg.go.dev/context) * [io](https://pkg.go.dev/io) * [io/fs](https://pkg.go.dev/io/fs) +* [io/ioutil](https://pkg.go.dev/io/ioutil) * [log](https://pkg.go.dev/log) * [flag](https://pkg.go.dev/flag) * [sort](https://pkg.go.dev/sort) -* [strconv](https://pkg.go.dev/strconv) +* [bytes](https://pkg.go.dev/bytes) +* [bufio](https://pkg.go.dev/bufio) * [strings](https://pkg.go.dev/strings) +* [strconv](https://pkg.go.dev/strconv) +* [path](https://pkg.go.dev/path) +* [path/filepath](https://pkg.go.dev/path/filepath) * [sync/atomic](https://pkg.go.dev/sync/atomic) * [sync](https://pkg.go.dev/sync) (partially) * [syscall](https://pkg.go.dev/syscall) (partially) @@ -287,19 +296,39 @@ Here are the Go packages that can be imported correctly: * [fmt](https://pkg.go.dev/fmt) (partially) * [reflect](https://pkg.go.dev/reflect) (partially) * [time](https://pkg.go.dev/time) (partially) +* [encoding](https://pkg.go.dev/encoding) +* [encoding/binary](https://pkg.go.dev/encoding/binary) +* [encoding/hex](https://pkg.go.dev/encoding/hex) +* [encoding/base32](https://pkg.go.dev/encoding/base32) +* [encoding/base64](https://pkg.go.dev/encoding/base64) +* [encoding/csv](https://pkg.go.dev/encoding/csv) +* [hash](https://pkg.go.dev/hash) +* [hash/adler32](https://pkg.go.dev/hash/adler32) +* [hash/crc32](https://pkg.go.dev/hash/crc32) (partially) +* [hash/crc64](https://pkg.go.dev/hash/crc64) +* [crypto](https://pkg.go.dev/crypto) +* [crypto/md5](https://pkg.go.dev/crypto/md5) +* [crypto/sha1](https://pkg.go.dev/crypto/sha1) +* [crypto/sha256](https://pkg.go.dev/crypto/sha256) +* [crypto/sha512](https://pkg.go.dev/crypto/sha512) (partially) +* [crypto/rand](https://pkg.go.dev/crypto/rand) (partially) +* [regexp](https://pkg.go.dev/regexp) +* [regexp/syntax](https://pkg.go.dev/regexp/syntax) +* [go/token](https://pkg.go.dev/go/token) +* [go/scanner](https://pkg.go.dev/go/scanner) ## Dependencies -- [Go 1.20+](https://go.dev) (build only) +- [Go 1.20+](https://go.dev) - [LLVM 18](https://llvm.org) - [LLD 18](https://lld.llvm.org) - [Clang 18](https://clang.llvm.org) - [pkg-config 0.29+](https://www.freedesktop.org/wiki/Software/pkg-config/) - [bdwgc/libgc 8.0+](https://www.hboehm.info/gc/) -- [cJSON 1.7+](https://github.com/DaveGamble/cJSON) (optional, for [github.com/goplus/llgo/c/cjson](https://pkg.go.dev/github.com/goplus/llgo/c/cjson)) -- [SQLite 3](https://www.sqlite.org) (optional, for [github.com/goplus/llgo/c/sqlite](https://pkg.go.dev/github.com/goplus/llgo/c/sqlite)) -- [Python 3.11+](https://www.python.org) (optional, for [github.com/goplus/llgo/py](https://pkg.go.dev/github.com/goplus/llgo/py)) +- [OpenSSL 3.0+](https://www.openssl.org/) +- [zlib 1.2+](https://www.zlib.net) +- [Python 3.12+](https://www.python.org) (optional, for [github.com/goplus/llgo/py](https://pkg.go.dev/github.com/goplus/llgo/py)) ## How to install @@ -308,10 +337,9 @@ Follow these steps to generate the `llgo` command (its usage is the same as the ### on macOS ```sh -brew update # execute if needed -brew install llvm@18 pkg-config libgc -brew install cjson sqlite python@3.12 # optional -export PATH=$(brew --prefix llvm@18)/bin:$PATH # you may want to add this to your shell RC file, e.g. ~/.zshrc +brew update +brew install llvm@18 pkg-config bdw-gc openssl +brew install python@3.12 # optional go install -v github.com/goplus/llgo/cmd/llgo@latest ``` @@ -320,10 +348,9 @@ go install -v github.com/goplus/llgo/cmd/llgo@latest ```sh echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-18 main" | sudo tee /etc/apt/sources.list.d/llvm.list wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - -sudo apt-get update # execute if needed -sudo apt-get install -y llvm-18-dev clang-18 lld-18 pkg-config libgc-dev -sudo apt-get install -y libcjson-dev libsqlite3-dev python3.12-dev # optional -export PATH=/usr/lib/llvm-18/bin:$PATH # you may want to add this to your shell RC file, e.g. ~/.bashrc +sudo apt-get update +sudo apt-get install -y llvm-18-dev clang-18 lld-18 pkg-config libgc-dev libssl-dev zlib1g-dev +sudo apt-get install -y python3.12-dev # optional go install -v github.com/goplus/llgo/cmd/llgo@latest ``` diff --git a/_cmptest/_bigsqrt2/sqrt2.go b/_cmptest/_bigsqrt2/sqrt2.go new file mode 100644 index 00000000..156bb800 --- /dev/null +++ b/_cmptest/_bigsqrt2/sqrt2.go @@ -0,0 +1,47 @@ +package main + +import ( + "fmt" + "math" + "math/big" +) + +func main() { + // We'll do computations with 200 bits of precision in the mantissa. + const prec = 200 + + // Compute the square root of 2 using Newton's Method. We start with + // an initial estimate for sqrt(2), and then iterate: + // x_{n+1} = 1/2 * ( x_n + (2.0 / x_n) ) + + // Since Newton's Method doubles the number of correct digits at each + // iteration, we need at least log_2(prec) steps. + steps := int(math.Log2(prec)) + + // Initialize values we need for the computation. + two := new(big.Float).SetPrec(prec).SetInt64(2) + half := new(big.Float).SetPrec(prec).SetFloat64(0.5) + + // Use 1 as the initial estimate. + x := new(big.Float).SetPrec(prec).SetInt64(1) + + // We use t as a temporary variable. There's no need to set its precision + // since big.Float values with unset (== 0) precision automatically assume + // the largest precision of the arguments when used as the result (receiver) + // of a big.Float operation. + t := new(big.Float) + + // Iterate. + for i := 0; i <= steps; i++ { + t.Quo(two, x) // t = 2.0 / x_n + t.Add(x, t) // t = x_n + (2.0 / x_n) + x.Mul(half, t) // x_{n+1} = 0.5 * t + } + + // We can use the usual fmt.Printf verbs since big.Float implements fmt.Formatter + fmt.Printf("sqrt(2) = %.50f\n", x) + + // Print the error between 2 and x*x. + t.Mul(x, x) // t = x*x + fmt.Printf("error = %e\n", t.Sub(two, t)) +} diff --git a/_cmptest/_goparsedemo/parse.go b/_cmptest/_goparsedemo/parse.go new file mode 100644 index 00000000..bba0347c --- /dev/null +++ b/_cmptest/_goparsedemo/parse.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "go/parser" + "go/token" +) + +func main() { + fset := token.NewFileSet() // positions are relative to fset + + src := `package foo + +import ( + "fmt" + "time" +) + +func bar() { + fmt.Println(time.Now()) +}` + + // Parse src but stop after processing the imports. + f, err := parser.ParseFile(fset, "", src, parser.ImportsOnly) + if err != nil { + fmt.Println(err) + return + } + + // Print the imports from the file's AST. + for _, s := range f.Imports { + fmt.Println(s.Path.Value) + } +} diff --git a/_cmptest/_jsondemo/json.go b/_cmptest/_jsondemo/json.go new file mode 100644 index 00000000..6f39c82e --- /dev/null +++ b/_cmptest/_jsondemo/json.go @@ -0,0 +1,16 @@ +package main + +import ( + "encoding/json" + "fmt" + "unsafe" +) + +func main() { + s := `{"name":"math","items":[{"name":"sqrt","sig":"(x, /)"},{"name":"pi"}]}` + data := unsafe.Slice(unsafe.StringData(s), len(s)) + var v any + json.Unmarshal(data, &v) + b, _ := json.MarshalIndent(v, "", " ") + fmt.Println(string(b)) +} diff --git a/_cmptest/_timeout/timer.go b/_cmptest/_timeout/timer.go index bc060890..a830f85e 100644 --- a/_cmptest/_timeout/timer.go +++ b/_cmptest/_timeout/timer.go @@ -13,7 +13,7 @@ func main() { select { case m := <-c: handle(m) - case <-time.After(10 * time.Second): + case <-time.After(time.Second / 10): fmt.Println("timed out") } } diff --git a/_cmptest/base64demo/base64.go b/_cmptest/base64demo/base64.go new file mode 100644 index 00000000..0fbd1fa4 --- /dev/null +++ b/_cmptest/base64demo/base64.go @@ -0,0 +1,48 @@ +package main + +import ( + "encoding/base32" + "encoding/base64" + "encoding/hex" + "fmt" + "log" +) + +func base64Demo() { + msg := "Hello, 世界" + encoded := base64.StdEncoding.EncodeToString([]byte(msg)) + fmt.Println(encoded) + decoded, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + fmt.Println("decode error:", err) + return + } + fmt.Println(string(decoded)) +} + +func base32Demo() { + str := "JBSWY3DPFQQHO33SNRSCC===" + dst := make([]byte, base32.StdEncoding.DecodedLen(len(str))) + n, err := base32.StdEncoding.Decode(dst, []byte(str)) + if err != nil { + fmt.Println("decode error:", err) + return + } + dst = dst[:n] + fmt.Printf("%q\n", dst) +} + +func hexDemo() { + const s = "48656c6c6f20476f7068657221" + decoded, err := hex.DecodeString(s) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%s\n", decoded) + +} +func main() { + base64Demo() + base32Demo() + hexDemo() +} diff --git a/_cmptest/bigintdemo/fib.go b/_cmptest/bigintdemo/fib.go new file mode 100644 index 00000000..9876c2e0 --- /dev/null +++ b/_cmptest/bigintdemo/fib.go @@ -0,0 +1,25 @@ +package main + +import ( + "fmt" + "math/big" +) + +func main() { + // Initialize two big ints with the first two numbers in the sequence. + a := big.NewInt(0) + b := big.NewInt(1) + + // Initialize limit as 10^99, the smallest integer with 100 digits. + var limit big.Int + limit.Exp(big.NewInt(10), big.NewInt(99), nil) + + // Loop while a is smaller than 1e100. + for a.Cmp(&limit) < 0 { + // Compute the next Fibonacci number, storing it in a. + a.Add(a, b) + // Swap a and b so that b is the next number in the sequence. + a, b = b, a + } + fmt.Println(a) // 100-digit Fibonacci number +} diff --git a/_cmptest/crcdemo/crc.go b/_cmptest/crcdemo/crc.go new file mode 100644 index 00000000..b2d6eaae --- /dev/null +++ b/_cmptest/crcdemo/crc.go @@ -0,0 +1,28 @@ +package main + +import ( + "fmt" + "hash/adler32" + "hash/crc32" + "hash/crc64" +) + +func crc64Demo() { + crc := crc64.MakeTable(crc64.ECMA) + fmt.Printf("%016x\n", crc64.Checksum([]byte("Hello world"), crc)) +} + +func crc32Demo() { + crc32q := crc32.MakeTable(crc32.IEEE) + fmt.Printf("%08x\n", crc32.Checksum([]byte("Hello world"), crc32q)) +} + +func adler32Demo() { + fmt.Printf("%08x\n", adler32.Checksum([]byte("Hello world"))) +} + +func main() { + adler32Demo() + crc32Demo() + crc64Demo() +} diff --git a/_cmptest/csvdemo/csv.go b/_cmptest/csvdemo/csv.go new file mode 100644 index 00000000..d4beec09 --- /dev/null +++ b/_cmptest/csvdemo/csv.go @@ -0,0 +1,30 @@ +package main + +import ( + "encoding/csv" + "fmt" + "io" + "log" + "strings" +) + +func main() { + in := `first_name,last_name,username +"Rob","Pike",rob +Ken,Thompson,ken +"Robert","Griesemer","gri" +` + r := csv.NewReader(strings.NewReader(in)) + + for { + record, err := r.Read() + if err == io.EOF { + break + } + if err != nil { + log.Fatal(err) + } + + fmt.Println(record) + } +} diff --git a/_cmptest/envexpand/expand.go b/_cmptest/envexpand/expand.go new file mode 100644 index 00000000..0c4baaf0 --- /dev/null +++ b/_cmptest/envexpand/expand.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + "os" +) + +func main() { + mapper := func(placeholderName string) string { + switch placeholderName { + case "DAY_PART": + return "morning" + case "NAME": + return "Gopher" + } + return "" + } + + fmt.Println(os.Expand("Good ${DAY_PART}, $NAME!", mapper)) +} diff --git a/_cmptest/goscandemo/scan.go b/_cmptest/goscandemo/scan.go new file mode 100644 index 00000000..76cdc261 --- /dev/null +++ b/_cmptest/goscandemo/scan.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "go/scanner" + "go/token" +) + +func main() { + // src is the input that we want to tokenize. + src := []byte("cos(x) + 1i*sin(x) // Euler") + + // Initialize the scanner. + var s scanner.Scanner + fset := token.NewFileSet() // positions are relative to fset + file := fset.AddFile("", fset.Base(), len(src)) // register input "file" + s.Init(file, src, nil /* no error handler */, scanner.ScanComments) + + // Repeated calls to Scan yield the token sequence found in the input. + for { + pos, tok, lit := s.Scan() + if tok == token.EOF { + break + } + fmt.Printf("%s\t%s\t%q\n", fset.Position(pos), tok, lit) + } +} diff --git a/_cmptest/ioutildemo/ioutil.go b/_cmptest/ioutildemo/ioutil.go new file mode 100644 index 00000000..723068e7 --- /dev/null +++ b/_cmptest/ioutildemo/ioutil.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "strings" +) + +func main() { + r := strings.NewReader("Go is a general-purpose language designed with systems programming in mind.") + + b, err := ioutil.ReadAll(r) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("%s\n", b) +} diff --git a/_cmptest/md5demo/md5.go b/_cmptest/md5demo/md5.go new file mode 100644 index 00000000..ebe57948 --- /dev/null +++ b/_cmptest/md5demo/md5.go @@ -0,0 +1,20 @@ +package main + +import ( + "crypto" + "crypto/md5" + "fmt" + "io" +) + +func main() { + h := md5.New() + io.WriteString(h, "The fog is getting thicker!") + io.WriteString(h, "And Leon's getting laaarger!") + fmt.Printf("%x\n", h.Sum(nil)) + + h = crypto.MD5.New() + io.WriteString(h, "The fog is getting thicker!") + io.WriteString(h, "And Leon's getting laaarger!") + fmt.Printf("%x\n", h.Sum(nil)) +} diff --git a/_cmptest/_osexec/exec.go b/_cmptest/osexec/exec.go similarity index 68% rename from _cmptest/_osexec/exec.go rename to _cmptest/osexec/exec.go index ec983c4b..0ec5c09e 100644 --- a/_cmptest/_osexec/exec.go +++ b/_cmptest/osexec/exec.go @@ -1,9 +1,12 @@ package main import ( + "fmt" "os" "os/exec" "runtime" + + "github.com/goplus/llgo/xtool/env/llvm" ) func main() { @@ -15,4 +18,7 @@ func main() { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Run() + + dir := llvm.New("").BinDir() + fmt.Println(dir) } diff --git a/_cmptest/pipedemo/pipe.go b/_cmptest/pipedemo/pipe.go new file mode 100644 index 00000000..1d60a08c --- /dev/null +++ b/_cmptest/pipedemo/pipe.go @@ -0,0 +1,26 @@ +package main + +import ( + "fmt" + "io" +) + +func main() { + data := []byte("This is some data that needs to be stored in Body.") + pr, pw := io.Pipe() + go func() { + defer pw.Close() + if _, err := pw.Write(data); err != nil { + fmt.Println("Error writing to pipe:", err) + return + } + }() + defer pr.Close() + + readData, err := io.ReadAll(pr) + if err != nil { + fmt.Println("Error reading from Body:", err) + return + } + fmt.Println("Body:", string(readData)) +} diff --git a/_cmptest/printfdemo/demo.go b/_cmptest/printfdemo/demo.go new file mode 100644 index 00000000..36a2afeb --- /dev/null +++ b/_cmptest/printfdemo/demo.go @@ -0,0 +1,12 @@ +package main + +import ( + "fmt" + + "github.com/goplus/llgo/xtool/nm" +) + +func main() { + sym := nm.Symbol{Name: "abc", Type: nm.Text} + fmt.Printf("%016x %c %s\n", sym.Addr, sym.Type, sym.Name) +} diff --git a/_cmptest/readfiledemo/readf.go b/_cmptest/readfiledemo/readf.go new file mode 100644 index 00000000..7f6c7bb4 --- /dev/null +++ b/_cmptest/readfiledemo/readf.go @@ -0,0 +1,25 @@ +package main + +import ( + "fmt" + "os" +) + +func main() { + fileName := "err.log" + os.WriteFile(fileName, []byte("123"), 0644) + + _, err := os.Stat(fileName) + if os.IsNotExist(err) { + fmt.Fprintf(os.Stderr, "File %s not found\n", fileName) + return + } + + data, err := os.ReadFile(fileName) + if err != nil { + fmt.Fprintf(os.Stderr, "ReadFile: %v\n", err) + return + } + + fmt.Printf("%s\n", data) +} diff --git a/_cmptest/regexdemo/regex.go b/_cmptest/regexdemo/regex.go new file mode 100644 index 00000000..7153a2e9 --- /dev/null +++ b/_cmptest/regexdemo/regex.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" + + "github.com/goplus/llgo/xtool/env" +) + +func main() { + fmt.Println(env.ExpandEnv("$(pkg-config --libs bdw-gc)")) +} diff --git a/_cmptest/sha1demo/sha1.go b/_cmptest/sha1demo/sha1.go new file mode 100644 index 00000000..dbd813c7 --- /dev/null +++ b/_cmptest/sha1demo/sha1.go @@ -0,0 +1,14 @@ +package main + +import ( + "crypto/sha1" + "fmt" + "io" +) + +func main() { + h := sha1.New() + io.WriteString(h, "The fog is getting thicker!") + io.WriteString(h, "And Leon's getting laaarger!") + fmt.Printf("%x", h.Sum(nil)) +} diff --git a/_cmptest/sha256demo/sha256.go b/_cmptest/sha256demo/sha256.go new file mode 100644 index 00000000..1ef0652d --- /dev/null +++ b/_cmptest/sha256demo/sha256.go @@ -0,0 +1,14 @@ +package main + +import ( + "crypto/sha256" + "fmt" + "io" +) + +func main() { + h := sha256.New() + io.WriteString(h, "The fog is getting thicker!") + io.WriteString(h, "And Leon's getting laaarger!") + fmt.Printf("%x", h.Sum(nil)) +} diff --git a/_cmptest/sha512demo/sha512.go b/_cmptest/sha512demo/sha512.go new file mode 100644 index 00000000..f2bf3791 --- /dev/null +++ b/_cmptest/sha512demo/sha512.go @@ -0,0 +1,14 @@ +package main + +import ( + "crypto/sha512" + "fmt" + "io" +) + +func main() { + h := sha512.New() + io.WriteString(h, "The fog is getting thicker!") + io.WriteString(h, "And Leon's getting laaarger!") + fmt.Printf("%x", h.Sum(nil)) +} diff --git a/_demo/crand/rand.go b/_demo/crand/rand.go new file mode 100644 index 00000000..00df6fd2 --- /dev/null +++ b/_demo/crand/rand.go @@ -0,0 +1,26 @@ +package main + +import ( + "fmt" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/math/rand" + "github.com/goplus/llgo/c/time" +) + +func fastrand64() uint64 { + v1 := uint64(rand.Random()) + v2 := uint64(rand.Random()) + return v1 ^ (v2 << 32) +} + +func main() { + rand.Srand(c.Uint(time.Time(nil))) + fmt.Printf("%x\n", rand.Rand()) + fmt.Printf("%x\n", rand.Rand()) + + rand.Srandom(c.Uint(time.Time(nil))) + fmt.Printf("%x\n", rand.Random()) + fmt.Printf("%x\n", rand.Random()) + fmt.Printf("%x\n", fastrand64()) +} diff --git a/_demo/llama2-c/run.go b/_demo/llama2-c/run.go index d7b2102a..29077c10 100644 --- a/_demo/llama2-c/run.go +++ b/_demo/llama2-c/run.go @@ -3,6 +3,7 @@ package main import ( "github.com/goplus/llgo/c" "github.com/goplus/llgo/c/llama2" + "github.com/goplus/llgo/c/time" ) func main() { @@ -11,7 +12,7 @@ func main() { var tokenizerPath *c.Char = c.Str("tokenizer.bin") var temperature, topp c.Float = 1.0, 0.9 var steps c.Int = 256 - var rngSeed uint64 = uint64(c.Time(nil)) + var rngSeed uint64 = uint64(time.Time(nil)) loop: // parse command line arguments for { diff --git a/_demo/rand/rand.go b/_demo/rand/rand.go deleted file mode 100644 index 106398a9..00000000 --- a/_demo/rand/rand.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "github.com/goplus/llgo/c" - "github.com/goplus/llgo/c/math/rand" -) - -func main() { - var s c.Uint = 6 - rand.Srand(s) - rr := rand.RandR(&s) - r := rand.Rand() - println("r:", r) - println("rr:", rr) -} diff --git a/_demo/randcrypt/rand.go b/_demo/randcrypt/rand.go new file mode 100644 index 00000000..c8d52e63 --- /dev/null +++ b/_demo/randcrypt/rand.go @@ -0,0 +1,18 @@ +package main + +import ( + "crypto/rand" + "fmt" +) + +func main() { + c := 10 + b := make([]byte, c) + _, err := rand.Read(b) + if err != nil { + fmt.Println("error:", err) + return + } + // The slice should now contain random bytes instead of only zeroes. + fmt.Printf("%x\n", b) +} diff --git a/_demo/randdemo/rand.go b/_demo/randdemo/rand.go new file mode 100644 index 00000000..23ce9ebe --- /dev/null +++ b/_demo/randdemo/rand.go @@ -0,0 +1,12 @@ +package main + +import ( + "fmt" + "math/rand" +) + +func main() { + fmt.Println(rand.Intn(100)) + fmt.Println(rand.Intn(100)) + fmt.Println(rand.Intn(100)) +} diff --git a/c/c.go b/c/c.go index 2b8eada1..f72670b9 100644 --- a/c/c.go +++ b/c/c.go @@ -227,9 +227,6 @@ func Perror(s *Char) // ----------------------------------------------------------------------------- -//go:linkname Time C.time -func Time(*int32) int32 - //go:linkname Usleep C.usleep func Usleep(useconds Uint) Int diff --git a/c/cjson/_demo/mkjson/mkjson.go b/c/cjson/_demo/mkjson/mkjson.go index e91db71f..6361d48b 100644 --- a/c/cjson/_demo/mkjson/mkjson.go +++ b/c/cjson/_demo/mkjson/mkjson.go @@ -22,6 +22,22 @@ func main() { mod.SetItem(c.Str("items"), syms) - c.Printf(c.Str("%s\n"), mod.CStr()) + cstr := mod.CStr() + str := c.GoString(cstr) + c.Printf(c.Str("%s\n"), cstr) + cjson.FreeCStr(cstr) + + mod.Delete() + + cjsonLoad(str) +} + +func cjsonLoad(str string) { + mod := cjson.ParseString(str) + + cstr := mod.Print() + c.Printf(c.Str("%s\n"), cstr) + cjson.FreeCStr(cstr) + mod.Delete() } diff --git a/c/cjson/cjson.go b/c/cjson/cjson.go index 03a8e5ec..af969e2d 100644 --- a/c/cjson/cjson.go +++ b/c/cjson/cjson.go @@ -17,7 +17,7 @@ package cjson import ( - _ "unsafe" + "unsafe" "github.com/goplus/llgo/c" ) @@ -31,6 +31,20 @@ type JSON struct { Unused [0]byte } +//go:linkname Parse C.cJSON_Parse +func Parse(value *c.Char) *JSON + +//go:linkname ParseWithLength C.cJSON_ParseWithLength +func ParseWithLength(value *byte, valueLength uintptr) *JSON + +func ParseBytes(value []byte) *JSON { + return ParseWithLength(unsafe.SliceData(value), uintptr(len(value))) +} + +func ParseString(value string) *JSON { + return ParseWithLength(unsafe.StringData(value), uintptr(len(value))) +} + //go:linkname Null C.cJSON_CreateNull func Null() *JSON @@ -119,3 +133,21 @@ func (o *JSON) PrintUnformatted() *c.Char { return nil } // // llgo:link (*JSON).PrintBuffered C.cJSON_PrintBuffered func (o *JSON) PrintBuffered(prebuffer c.Int, fmt c.Int) *c.Char { return nil } + +// llgo:link (*JSON).GetObjectItemCaseSensitive C.cJSON_GetObjectItemCaseSensitive +func (o *JSON) GetObjectItemCaseSensitive(key *c.Char) *JSON { return nil } + +// llgo:link (*JSON).GetArraySize C.cJSON_GetArraySize +func (o *JSON) GetArraySize() c.Int { return 0 } + +// llgo:link (*JSON).GetArrayItem C.cJSON_GetArrayItem +func (o *JSON) GetArrayItem(index c.Int) *JSON { return nil } + +// llgo:link (*JSON).GetStringValue C.cJSON_GetStringValue +func (o *JSON) GetStringValue() *c.Char { return nil } + +//go:linkname Free C.cJSON_free +func Free(ptr unsafe.Pointer) + +//go:linkname FreeCStr C.cJSON_free +func FreeCStr(*c.Char) diff --git a/c/clang/_demo/symboldump/symboldump.go b/c/clang/_demo/symboldump/symboldump.go new file mode 100644 index 00000000..a2eb0448 --- /dev/null +++ b/c/clang/_demo/symboldump/symboldump.go @@ -0,0 +1,131 @@ +package main + +import ( + "fmt" + "os" + "unsafe" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/clang" +) + +type Context struct { + namespaceName string + className string +} + +func newContext() *Context { + return &Context{} +} + +func (c *Context) setNamespaceName(name string) { + c.namespaceName = name +} + +func (c *Context) setClassName(name string) { + c.className = name +} + +var context = newContext() + +func print_cursor_info(cursor clang.Cursor) { + loc := cursor.Location() + var file clang.File + var line, column c.Uint + + loc.SpellingLocation(&file, &line, &column, nil) + filename := file.FileName() + + c.Printf(c.Str("%s:%d:%d\n"), filename.CStr(), line, column) + + cursorStr := cursor.String() + symbol := cursor.Mangling() + defer symbol.Dispose() + defer cursorStr.Dispose() + defer filename.Dispose() + + if context.namespaceName != "" && context.className != "" { + fmt.Printf("%s:%s:", context.namespaceName, context.className) + } else if context.namespaceName != "" { + fmt.Printf("%s:", context.namespaceName) + } + c.Printf(c.Str("%s\n"), cursorStr.CStr()) + + if cursor.Kind == clang.CXXMethod || cursor.Kind == clang.FunctionDecl { + c.Printf(c.Str("symbol:%s\n"), symbol.CStr()) + + typeStr := cursor.ResultType().String() + defer typeStr.Dispose() + c.Printf(c.Str("Return Type: %s\n"), typeStr.CStr()) + c.Printf(c.Str("Parameters(%d): ( "), cursor.NumArguments()) + + for i := 0; i < int(cursor.NumArguments()); i++ { + argCurSor := cursor.Argument(c.Uint(i)) + argType := argCurSor.Type().String() + argName := argCurSor.String() + c.Printf(c.Str("%s %s"), argType.CStr(), argName.CStr()) + if i < int(cursor.NumArguments())-1 { + c.Printf(c.Str(", ")) + } + + argType.Dispose() + argName.Dispose() + } + + c.Printf(c.Str(" )\n")) + println("--------------------------------") + } +} + +func visit(cursor, parent clang.Cursor, clientData c.Pointer) clang.ChildVisitResult { + if cursor.Kind == clang.Namespace { + nameStr := cursor.String() + context.setNamespaceName(c.GoString(nameStr.CStr())) + clang.VisitChildren(cursor, visit, nil) + context.setNamespaceName("") + } else if cursor.Kind == clang.ClassDecl { + nameStr := cursor.String() + context.setClassName(c.GoString(nameStr.CStr())) + clang.VisitChildren(cursor, visit, nil) + context.setClassName("") + } else if cursor.Kind == clang.CXXMethod || cursor.Kind == clang.FunctionDecl { + print_cursor_info(cursor) + } + + return clang.ChildVisit_Continue +} + +func parse(filename *c.Char) { + index := clang.CreateIndex(0, 0) + args := make([]*c.Char, 3) + args[0] = c.Str("-x") + args[1] = c.Str("c++") + args[2] = c.Str("-std=c++11") + unit := index.ParseTranslationUnit( + filename, + unsafe.SliceData(args), 3, + nil, 0, + clang.TranslationUnit_None, + ) + + if unit == nil { + println("Unable to parse translation unit. Quitting.") + c.Exit(1) + } + + cursor := unit.Cursor() + + clang.VisitChildren(cursor, visit, nil) + unit.Dispose() + index.Dispose() +} + +func main() { + if c.Argc != 2 { + fmt.Fprintln(os.Stderr, "Usage: \n") + return + } else { + sourceFile := *c.Advance(c.Argv, 1) + parse(sourceFile) + } +} diff --git a/c/clang/_wrap/cursor.cpp b/c/clang/_wrap/cursor.cpp index f757fe3d..7b2df776 100644 --- a/c/clang/_wrap/cursor.cpp +++ b/c/clang/_wrap/cursor.cpp @@ -1,29 +1,50 @@ -#include #include +#include -typedef enum CXChildVisitResult(* wrap_CXCursorVisitor) (CXCursor *cursor, CXCursor *parent, CXClientData client_data); +typedef enum CXChildVisitResult (*wrap_CXCursorVisitor)(CXCursor *cursor, CXCursor *parent, CXClientData client_data); typedef struct { - CXClientData data; + CXClientData data; wrap_CXCursorVisitor visitor; } wrap_data; CXChildVisitResult wrap_visitor(CXCursor cursor, CXCursor parent, CXClientData data) { - wrap_data *d = (wrap_data*)(data); - return d->visitor(&cursor,&parent,d->data); + wrap_data *d = (wrap_data *)(data); + return d->visitor(&cursor, &parent, d->data); } extern "C" { -CXString wrap_clang_getCursorSpelling(CXCursor *cur) { - return clang_getCursorSpelling(*cur); +CXString wrap_clang_getCursorSpelling(CXCursor *cur) { return clang_getCursorSpelling(*cur); } + +CXString wrap_clang_Cursor_getMangling(CXCursor *cur) { return clang_Cursor_getMangling(*cur); } + +int wrap_clang_Cursor_getNumArguments(CXCursor *cur) { return clang_Cursor_getNumArguments(*cur); } + +void wrap_clang_Cursor_getArgument(CXCursor *C, unsigned i, CXCursor *argCur) { + *argCur = clang_Cursor_getArgument(*C, i); } -unsigned wrap_clang_visitChildren(CXCursor *parent, - wrap_CXCursorVisitor visitor, - CXClientData client_data) { - wrap_data data = {client_data,visitor}; - return clang_visitChildren(*parent,wrap_visitor,CXClientData(&data)); +void wrap_clang_getTranslationUnitCursor(CXTranslationUnit uint, CXCursor *cur) { + *cur = clang_getTranslationUnitCursor(uint); +} + +void wrap_clang_getCursorType(CXCursor *cur, CXType *typ) { *typ = clang_getCursorType(*cur); } + +void wrap_clang_getCursorResultType(CXCursor *cur, CXType *typ) { *typ = clang_getCursorResultType(*cur); } + +CXString wrap_clang_getTypeSpelling(CXType *typ) { return clang_getTypeSpelling(*typ); } + +void wrap_clang_getCursorLocation(CXCursor *cur, CXSourceLocation *loc) { *loc = clang_getCursorLocation(*cur); } + +void wrap_clang_getSpellingLocation(CXSourceLocation *loc, CXFile *file, unsigned *line, unsigned *column, + unsigned *offset) { + clang_getSpellingLocation(*loc, file, line, column, offset); +} + +unsigned wrap_clang_visitChildren(CXCursor *parent, wrap_CXCursorVisitor visitor, CXClientData client_data) { + wrap_data data = {client_data, visitor}; + return clang_visitChildren(*parent, wrap_visitor, CXClientData(&data)); } } // extern "C" diff --git a/c/clang/clang.go b/c/clang/clang.go index fe905e92..9bc123a5 100644 --- a/c/clang/clang.go +++ b/c/clang/clang.go @@ -27,6 +27,1104 @@ const ( LLGoPackage = "link: -L$(llvm-config --libdir) -lclang; -lclang" ) +const ( + /* Declarations */ + /** + * A declaration whose specific kind is not exposed via this + * interface. + * + * Unexposed declarations have the same operations as any other kind + * of declaration; one can extract their location information, + * spelling, find their definitions, etc. However, the specific kind + * of the declaration is not reported. + */ + UnexposedDecl CursorKind = iota + 1 + + /** A C or C++ struct. */ + StructDecl + + /** A C or C++ union. */ + UnionDecl + + /** A C++ class. */ + ClassDecl + + /** An enumeration. */ + EnumDecl + + /** + * A field (in C) or non-static data member (in C++) in a + * struct, union, or C++ class. + */ + FieldDecl + + /** An enumerator constant. */ + EnumConstantDecl + + /** A function. */ + FunctionDecl + + /** A variable. */ + VarDecl + + /** A function or method parameter. */ + ParmDecl + + /** An Objective-C \@interface. */ + ObjCInterfaceDecl + + /** An Objective-C \@interface for a category. */ + ObjCCategoryDecl + + /** An Objective-C \@protocol declaration. */ + ObjCProtocolDecl + + /** An Objective-C \@property declaration. */ + ObjCPropertyDecl + + /** An Objective-C instance variable. */ + ObjCIvarDecl + + /** An Objective-C instance method. */ + ObjCInstanceMethodDecl + + /** An Objective-C class method. */ + ObjCClassMethodDecl + + /** An Objective-C \@implementation. */ + ObjCImplementationDecl + + /** An Objective-C \@implementation for a category. */ + ObjCCategoryImplDecl + + /** A typedef. */ + TypedefDecl + + /** A C++ class method. */ + CXXMethod + + /** A C++ namespace. */ + Namespace + + /** A linkage specification, e.g. 'extern "C"'. */ + LinkageSpec + + /** A C++ constructor. */ + Constructor + + /** A C++ destructor. */ + Destructor + + /** A C++ conversion function. */ + ConversionFunction + + /** A C++ template type parameter. */ + TemplateTypeParameter + + /** A C++ non-type template parameter. */ + NonTypeTemplateParameter + + /** A C++ template template parameter. */ + TemplateTemplateParameter + + /** A C++ function template. */ + FunctionTemplate + + /** A C++ class template. */ + ClassTemplate + + /** A C++ class template partial specialization. */ + ClassTemplatePartialSpecialization + + /** A C++ namespace alias declaration. */ + NamespaceAlias + + /** A C++ using directive. */ + UsingDirective + + /** A C++ using declaration. */ + UsingDeclaration + + /** A C++ alias declaration */ + TypeAliasDecl + + /** An Objective-C \@synthesize definition. */ + ObjCSynthesizeDecl + + /** An Objective-C \@dynamic definition. */ + ObjCDynamicDecl + + /** An access specifier. */ + CXXAccessSpecifier + + FirstDecl = UnexposedDecl + LastDecl = CXXAccessSpecifier + + /* References */ + FirstRef = 40 + ObjCSuperClassRef = iota - 2 //40 + ObjCProtocolRef + ObjCClassRef + + /** + * A reference to a type declaration. + * + * A type reference occurs anywhere where a type is named but not + * declared. For example, given: + * + * \code + * typedef unsigned size_type; + * size_type size; + * \endcode + * + * The typedef is a declaration of size_type (CXCursor_TypedefDecl), + * while the type of the variable "size" is referenced. The cursor + * referenced by the type of size is the typedef for size_type. + */ + TypeRef + + CXXBaseSpecifier + + /** + * A reference to a class template, function template, template + * template parameter, or class template partial specialization. + */ + TemplateRef + + /** + * A reference to a namespace or namespace alias. + */ + NamespaceRef + + /** + * A reference to a member of a struct, union, or class that occurs in + * some non-expression context, e.g., a designated initializer. + */ + MemberRef + + /** + * A reference to a labeled statement. + * + * This cursor kind is used to describe the jump to "start_over" in the + * goto statement in the following example: + * + * \code + * start_over: + * ++counter; + * + * goto start_over; + * \endcode + * + * A label reference cursor refers to a label statement. + */ + LabelRef + + /** + * A reference to a set of overloaded functions or function templates + * that has not yet been resolved to a specific function or function template. + * + * An overloaded declaration reference cursor occurs in C++ templates where + * a dependent name refers to a function. For example: + * + * \code + * template void swap(T&, T&); + * + * struct X { ... }; + * void swap(X&, X&); + * + * template + * void reverse(T* first, T* last) { + * while (first < last - 1) { + * swap(*first, *--last); + * ++first; + * } + * } + * + * struct Y { }; + * void swap(Y&, Y&); + * \endcode + * + * Here, the identifier "swap" is associated with an overloaded declaration + * reference. In the template definition, "swap" refers to either of the two + * "swap" functions declared above, so both results will be available. At + * instantiation time, "swap" may also refer to other functions found via + * argument-dependent lookup (e.g., the "swap" function at the end of the + * example). + * + * The functions \c clang_getNumOverloadedDecls() and + * \c clang_getOverloadedDecl() can be used to retrieve the definitions + * referenced by this cursor. + */ + OverloadedDeclRef + + /** + * A reference to a variable that occurs in some non-expression + * context, e.g., a C++ lambda capture list. + */ + VariableRef + + LastRef = VariableRef + + /* Error conditions */ + FirstInvalid = 70 + InvalidFile = iota + 15 //70 + NoDeclFound + NotImplemented + InvalidCode + LastInvalid = InvalidCode + + /* Expressions */ + FirstExpr = 100 + + /** + * An expression whose specific kind is not exposed via this + * interface. + * + * Unexposed expressions have the same operations as any other kind + * of expression; one can extract their location information, + * spelling, children, etc. However, the specific kind of the + * expression is not reported. + */ + UnexposedExpr = iota + 39 //100 + + /** + * An expression that refers to some value declaration, such + * as a function, variable, or enumerator. + */ + DeclRefExpr + + /** + * An expression that refers to a member of a struct, union, + * class, Objective-C class, etc. + */ + MemberRefExpr + + /** An expression that calls a function. */ + CallExpr + + /** An expression that sends a message to an Objective-C + object or class. */ + ObjCMessageExpr + + /** An expression that represents a block literal. */ + BlockExpr + + /** An integer literal. + */ + IntegerLiteral + + /** A floating point number literal. + */ + FloatingLiteral + + /** An imaginary number literal. + */ + ImaginaryLiteral + + /** A string literal. + */ + StringLiteral + + /** A character literal. + */ + CharacterLiteral + + /** A parenthesized expression, e.g. "(1)". + * + * This AST node is only formed if full location information is requested. + */ + ParenExpr + + /** This represents the unary-expression's (except sizeof and + * alignof). + */ + UnaryOperator + + /** [C99 6.5.2.1] Array Subscripting. + */ + ArraySubscriptExpr + + /** A builtin binary operation expression such as "x + y" or + * "x <= y". + */ + BinaryOperator + + /** Compound assignment such as "+=". + */ + CompoundAssignOperator + + /** The ?: ternary operator. + */ + ConditionalOperator + + /** An explicit cast in C (C99 6.5.4) or a C-style cast in C++ + * (C++ [expr.cast]), which uses the syntax (Type)expr. + * + * For example: (int)f. + */ + CStyleCastExpr + + /** [C99 6.5.2.5] + */ + CompoundLiteralExpr + + /** Describes an C or C++ initializer list. + */ + InitListExpr + + /** The GNU address of label extension, representing &&label. + */ + AddrLabelExpr + + /** This is the GNU Statement Expression extension: ({int X=4; X;}) + */ + StmtExpr + + /** Represents a C11 generic selection. + */ + GenericSelectionExpr + + /** Implements the GNU __null extension, which is a name for a null + * pointer constant that has integral type (e.g., int or long) and is the same + * size and alignment as a pointer. + * + * The __null extension is typically only used by system headers, which define + * NULL as __null in C++ rather than using 0 (which is an integer that may not + * match the size of a pointer). + */ + GNUNullExpr + + /** C++'s static_cast<> expression. + */ + CXXStaticCastExpr + + /** C++'s dynamic_cast<> expression. + */ + CXXDynamicCastExpr + + /** C++'s reinterpret_cast<> expression. + */ + CXXReinterpretCastExpr + + /** C++'s const_cast<> expression. + */ + CXXConstCastExpr + + /** Represents an explicit C++ type conversion that uses "functional" + * notion (C++ [expr.type.conv]). + * + * Example: + * \code + * x = int(0.5); + * \endcode + */ + CXXFunctionalCastExpr + + /** A C++ typeid expression (C++ [expr.typeid]). + */ + CXXTypeidExpr + + /** [C++ 2.13.5] C++ Boolean Literal. + */ + CXXBoolLiteralExpr + + /** [C++0x 2.14.7] C++ Pointer Literal. + */ + CXXNullPtrLiteralExpr + + /** Represents the "this" expression in C++ + */ + CXXThisExpr + + /** [C++ 15] C++ Throw Expression. + * + * This handles 'throw' and 'throw' assignment-expression. When + * assignment-expression isn't present, Op will be null. + */ + CXXThrowExpr + + /** A new expression for memory allocation and constructor calls, e.g: + * "new CXXNewExpr(foo)". + */ + CXXNewExpr + + /** A delete expression for memory deallocation and destructor calls, + * e.g. "delete[] pArray". + */ + CXXDeleteExpr + + /** A unary expression. (noexcept, sizeof, or other traits) + */ + UnaryExpr + + /** An Objective-C string literal i.e. @"foo". + */ + ObjCStringLiteral + + /** An Objective-C \@encode expression. + */ + ObjCEncodeExpr + + /** An Objective-C \@selector expression. + */ + ObjCSelectorExpr + + /** An Objective-C \@protocol expression. + */ + ObjCProtocolExpr + + /** An Objective-C "bridged" cast expression, which casts between + * Objective-C pointers and C pointers, transferring ownership in the process. + * + * \code + * NSString *str = (__bridge_transfer NSString *)CFCreateString(); + * \endcode + */ + ObjCBridgedCastExpr + + /** Represents a C++0x pack expansion that produces a sequence of + * expressions. + * + * A pack expansion expression contains a pattern (which itself is an + * expression) followed by an ellipsis. For example: + * + * \code + * template + * void forward(F f, Types &&...args) { + * f(static_cast(args)...); + * } + * \endcode + */ + PackExpansionExpr + + /** Represents an expression that computes the length of a parameter + * pack. + * + * \code + * template + * struct count { + * static const unsigned value = sizeof...(Types); + * }; + * \endcode + */ + SizeOfPackExpr + /* Represents a C++ lambda expression that produces a local function + * object. + * + * \code + * void abssort(float *x, unsigned N) { + * std::sort(x, x + N, + * [](float a, float b) { + * return std::abs(a) < std::abs(b); + * }); + * } + * \endcode + */ + LambdaExpr + + /** Objective-c Boolean Literal. + */ + ObjCBoolLiteralExpr + + /** Represents the "self" expression in an Objective-C method. + */ + ObjCSelfExpr + + /** OpenMP 5.0 [2.1.5, Array Section]. + */ + OMPArraySectionExpr + + /** Represents an @available(...) check. + */ + ObjCAvailabilityCheckExpr + + /** + * Fixed point literal + */ + FixedPointLiteral + + /** OpenMP 5.0 [2.1.4, Array Shaping]. + */ + OMPArrayShapingExpr + + /** + * OpenMP 5.0 [2.1.6 Iterators] + */ + OMPIteratorExpr + + /** OpenCL's addrspace_cast<> expression. + */ + CXXAddrspaceCastExpr + + /** + * Expression that references a C++20 concept. + */ + ConceptSpecializationExpr + + /** + * Expression that references a C++20 concept. + */ + RequiresExpr + + /** + * Expression that references a C++20 parenthesized list aggregate + * initializer. + */ + CXXParenListInitExpr + + LastExpr = CXXParenListInitExpr + + /* Statements */ + FirstStmt = 200 + /** + * A statement whose specific kind is not exposed via this + * interface. + * + * Unexposed statements have the same operations as any other kind of + * statement; one can extract their location information, spelling, + * children, etc. However, the specific kind of the statement is not + * reported. + */ + UnexposedStmt = iota + 81 //200 + + /** A labelled statement in a function. + * + * This cursor kind is used to describe the "start_over:" label statement in + * the following example: + * + * \code + * start_over: + * ++counter; + * \endcode + * + */ + LabelStmt + + /** A group of statements like { stmt stmt }. + * + * This cursor kind is used to describe compound statements, e.g. function + * bodies. + */ + CompoundStmt + + /** A case statement. + */ + CaseStmt + + /** A default statement. + */ + DefaultStmt + + /** An if statement + */ + IfStmt + + /** A switch statement. + */ + SwitchStmt + + /** A while statement. + */ + WhileStmt + + /** A do statement. + */ + DoStmt + + /** A for statement. + */ + ForStmt + + /** A goto statement. + */ + GotoStmt + + /** An indirect goto statement. + */ + IndirectGotoStmt + + /** A continue statement. + */ + ContinueStmt + + /** A break statement. + */ + BreakStmt + + /** A return statement. + */ + ReturnStmt + + /** A GCC inline assembly statement extension. + */ + GCCAsmStmt + AsmStmt = GCCAsmStmt + + /** Objective-C's overall \@try-\@catch-\@finally statement. + */ + ObjCAtTryStmt = iota + 80 //216 + + /** Objective-C's \@catch statement. + */ + ObjCAtCatchStmt + + /** Objective-C's \@finally statement. + */ + ObjCAtFinallyStmt + + /** Objective-C's \@throw statement. + */ + ObjCAtThrowStmt + + /** Objective-C's \@synchronized statement. + */ + ObjCAtSynchronizedStmt + + /** Objective-C's autorelease pool statement. + */ + ObjCAutoreleasePoolStmt + + /** Objective-C's collection statement. + */ + ObjCForCollectionStmt + + /** C++'s catch statement. + */ + CXXCatchStmt + + /** C++'s try statement. + */ + CXXTryStmt + + /** C++'s for (* : *) statement. + */ + CXXForRangeStmt + + /** Windows Structured Exception Handling's try statement. + */ + SEHTryStmt + + /** Windows Structured Exception Handling's except statement. + */ + SEHExceptStmt + + /** Windows Structured Exception Handling's finally statement. + */ + SEHFinallyStmt + + /** A MS inline assembly statement extension. + */ + MSAsmStmt + + /** The null statement ";": C99 6.8.3p3. + * + * This cursor kind is used to describe the null statement. + */ + NullStmt + + /** Adaptor class for mixing declarations with statements and + * expressions. + */ + DeclStmt + + /** OpenMP parallel directive. + */ + OMPParallelDirective + + /** OpenMP SIMD directive. + */ + OMPSimdDirective + + /** OpenMP for directive. + */ + OMPForDirective + + /** OpenMP sections directive. + */ + OMPSectionsDirective + + /** OpenMP section directive. + */ + OMPSectionDirective + + /** OpenMP single directive. + */ + OMPSingleDirective + + /** OpenMP parallel for directive. + */ + OMPParallelForDirective + + /** OpenMP parallel sections directive. + */ + OMPParallelSectionsDirective + + /** OpenMP task directive. + */ + OMPTaskDirective + + /** OpenMP master directive. + */ + OMPMasterDirective + + /** OpenMP critical directive. + */ + OMPCriticalDirective + + /** OpenMP taskyield directive. + */ + OMPTaskyieldDirective + + /** OpenMP barrier directive. + */ + OMPBarrierDirective + + /** OpenMP taskwait directive. + */ + OMPTaskwaitDirective + + /** OpenMP flush directive. + */ + OMPFlushDirective + + /** Windows Structured Exception Handling's leave statement. + */ + SEHLeaveStmt + + /** OpenMP ordered directive. + */ + OMPOrderedDirective + + /** OpenMP atomic directive. + */ + OMPAtomicDirective + + /** OpenMP for SIMD directive. + */ + OMPForSimdDirective + + /** OpenMP parallel for SIMD directive. + */ + OMPParallelForSimdDirective + + /** OpenMP target directive. + */ + OMPTargetDirective + + /** OpenMP teams directive. + */ + OMPTeamsDirective + + /** OpenMP taskgroup directive. + */ + OMPTaskgroupDirective + + /** OpenMP cancellation point directive. + */ + OMPCancellationPointDirective + + /** OpenMP cancel directive. + */ + OMPCancelDirective + + /** OpenMP target data directive. + */ + OMPTargetDataDirective + + /** OpenMP taskloop directive. + */ + OMPTaskLoopDirective + + /** OpenMP taskloop simd directive. + */ + OMPTaskLoopSimdDirective + + /** OpenMP distribute directive. + */ + OMPDistributeDirective + + /** OpenMP target enter data directive. + */ + OMPTargetEnterDataDirective + + /** OpenMP target exit data directive. + */ + OMPTargetExitDataDirective + + /** OpenMP target parallel directive. + */ + OMPTargetParallelDirective + + /** OpenMP target parallel for directive. + */ + OMPTargetParallelForDirective + + /** OpenMP target update directive. + */ + OMPTargetUpdateDirective + + /** OpenMP distribute parallel for directive. + */ + OMPDistributeParallelForDirective + + /** OpenMP distribute parallel for simd directive. + */ + OMPDistributeParallelForSimdDirective + + /** OpenMP distribute simd directive. + */ + OMPDistributeSimdDirective + + /** OpenMP target parallel for simd directive. + */ + OMPTargetParallelForSimdDirective + + /** OpenMP target simd directive. + */ + OMPTargetSimdDirective + + /** OpenMP teams distribute directive. + */ + OMPTeamsDistributeDirective + + /** OpenMP teams distribute simd directive. + */ + OMPTeamsDistributeSimdDirective + + /** OpenMP teams distribute parallel for simd directive. + */ + OMPTeamsDistributeParallelForSimdDirective + + /** OpenMP teams distribute parallel for directive. + */ + OMPTeamsDistributeParallelForDirective + + /** OpenMP target teams directive. + */ + OMPTargetTeamsDirective + + /** OpenMP target teams distribute directive. + */ + OMPTargetTeamsDistributeDirective + + /** OpenMP target teams distribute parallel for directive. + */ + OMPTargetTeamsDistributeParallelForDirective + + /** OpenMP target teams distribute parallel for simd directive. + */ + OMPTargetTeamsDistributeParallelForSimdDirective + + /** OpenMP target teams distribute simd directive. + */ + OMPTargetTeamsDistributeSimdDirective + + /** C++2a std::bit_cast expression. + */ + BuiltinBitCastExpr + + /** OpenMP master taskloop directive. + */ + OMPMasterTaskLoopDirective + + /** OpenMP parallel master taskloop directive. + */ + OMPParallelMasterTaskLoopDirective + + /** OpenMP master taskloop simd directive. + */ + OMPMasterTaskLoopSimdDirective + + /** OpenMP parallel master taskloop simd directive. + */ + OMPParallelMasterTaskLoopSimdDirective + + /** OpenMP parallel master directive. + */ + OMPParallelMasterDirective + + /** OpenMP depobj directive. + */ + OMPDepobjDirective + + /** OpenMP scan directive. + */ + OMPScanDirective + + /** OpenMP tile directive. + */ + OMPTileDirective + + /** OpenMP canonical loop. + */ + OMPCanonicalLoop + + /** OpenMP interop directive. + */ + OMPInteropDirective + + /** OpenMP dispatch directive. + */ + OMPDispatchDirective + + /** OpenMP masked directive. + */ + OMPMaskedDirective + + /** OpenMP unroll directive. + */ + OMPUnrollDirective + + /** OpenMP metadirective directive. + */ + OMPMetaDirective + + /** OpenMP loop directive. + */ + OMPGenericLoopDirective + + /** OpenMP teams loop directive. + */ + OMPTeamsGenericLoopDirective + + /** OpenMP target teams loop directive. + */ + OMPTargetTeamsGenericLoopDirective + + /** OpenMP parallel loop directive. + */ + OMPParallelGenericLoopDirective + + /** OpenMP target parallel loop directive. + */ + OMPTargetParallelGenericLoopDirective + + /** OpenMP parallel masked directive. + */ + OMPParallelMaskedDirective + + /** OpenMP masked taskloop directive. + */ + OMPMaskedTaskLoopDirective + + /** OpenMP masked taskloop simd directive. + */ + OMPMaskedTaskLoopSimdDirective + + /** OpenMP parallel masked taskloop directive. + */ + OMPParallelMaskedTaskLoopDirective + + /** OpenMP parallel masked taskloop simd directive. + */ + OMPParallelMaskedTaskLoopSimdDirective + + /** OpenMP error directive. + */ + OMPErrorDirective + + /** OpenMP scope directive. + */ + OMPScopeDirective + + LastStmt = OMPScopeDirective + + /** + * Cursor that represents the translation unit itself. + * + * The translation unit cursor exists primarily to act as the root + * cursor for traversing the contents of a translation unit. + */ + CursorTranslationUnit = 350 + + /* Attributes */ + FirstAttr = 400 + /** + * An attribute whose specific kind is not exposed via this + * interface. + */ + UnexposedAttr = iota + 170 + + IBActionAttr + IBOutletAttr + IBOutletCollectionAttr + CXXFinalAttr + CXXOverrideAttr + AnnotateAttr + AsmLabelAttr + PackedAttr + PureAttr + ConstAttr + NoDuplicateAttr + CUDAConstantAttr + CUDADeviceAttr + CUDAGlobalAttr + CUDAHostAttr + CUDASharedAttr + VisibilityAttr + DLLExport + DLLImport + NSReturnsRetained + NSReturnsNotRetained + NSReturnsAutoreleased + NSConsumesSelf + NSConsumed + ObjCException + ObjCNSObject + ObjCIndependentClass + ObjCPreciseLifetime + ObjCReturnsInnerPointer + ObjCRequiresSuper + ObjCRootClass + ObjCSubclassingRestricted + ObjCExplicitProtocolImpl + ObjCDesignatedInitializer + ObjCRuntimeVisible + ObjCBoxable + FlagEnum + ConvergentAttr + WarnUnusedAttr + WarnUnusedResultAttr + AlignedAttr + LastAttr = AlignedAttr + + /* Preprocessing */ + PreprocessingDirective = iota + 227 //500 + MacroDefinition + MacroExpansion + MacroInstantiation = MacroExpansion + InclusionDirective = 503 + FirstPreprocessing = PreprocessingDirective + LastPreprocessing = InclusionDirective + + /* Extra Declarations */ + /** + * A module import declaration. + */ + ModuleImportDecl = iota + 320 //600 + TypeAliasTemplateDecl + /** + * A static_assert or _Static_assert node + */ + StaticAssert + /** + * a friend declaration. + */ + FriendDecl + /** + * a concept declaration. + */ + ConceptDecl + + FirstExtraDecl = ModuleImportDecl + LastExtraDecl = ConceptDecl + + /** + * A code completion overload candidate. + */ + OverloadCandidate = 700 +) + /** * Opaque pointer representing client data that will be passed through * to various callbacks and visitors. @@ -162,8 +1260,12 @@ func (*TranslationUnit) Dispose() {} * The translation unit cursor can be used to start traversing the * various declarations within the given translation unit. */ -// llgo:link (*TranslationUnit).Cursor C.clang_getTranslationUnitCursor -func (*TranslationUnit) Cursor() (ret Cursor) { + +//llgo:link (*TranslationUnit).wrapCursor C.wrap_clang_getTranslationUnitCursor +func (t *TranslationUnit) wrapCursor(cursor *Cursor) {} + +func (t *TranslationUnit) Cursor() (ret Cursor) { + t.wrapCursor(&ret) return } @@ -198,8 +1300,34 @@ func (CursorKind) String() (ret String) { */ type Cursor struct { Kind CursorKind - Xdata c.Int - Data [3]c.Pointer + xdata c.Int + data [3]c.Pointer +} + +/** + * The type of an element in the abstract syntax tree. + * + */ +type Type struct { + Kind CursorKind + data [2]c.Pointer +} + +/** + * A particular source file that is part of a translation unit. + */ +type File uintptr + +/** + * Identifies a specific source location within a translation + * unit. + * + * Use clang_getExpansionLocation() or clang_getSpellingLocation() + * to map a source location to a particular file, line, and column. + */ +type SourceLocation struct { + ptrData [2]c.Pointer + intData c.Uint } /** @@ -214,6 +1342,106 @@ func (c Cursor) String() (ret String) { return c.wrapString() } +/** + * Retrieve a name for the entity referenced by this cursor. + */ +// llgo:link (*Cursor).wrapMangling C.wrap_clang_Cursor_getMangling +func (*Cursor) wrapMangling() (ret String) { + return +} + +func (c Cursor) Mangling() (ret String) { + return c.wrapMangling() +} + +/** + * Retrieve the type of a CXCursor (if any). + */ +// llgo:link (*Cursor).wrapType C.wrap_clang_getCursorType +func (c *Cursor) wrapType(ret *Type) {} + +func (c Cursor) Type() (ret Type) { + c.wrapType(&ret) + return +} + +/** + * Retrieve the return type associated with a given cursor. + * + * This only returns a valid type if the cursor refers to a function or method. + */ +// llgo:link (*Cursor).wrapResultType C.wrap_clang_getCursorResultType +func (c *Cursor) wrapResultType(ret *Type) {} + +func (c Cursor) ResultType() (ret Type) { + c.wrapResultType(&ret) + return +} + +/** + * Retrieve the number of non-variadic arguments associated with a given + * cursor. + * + * The number of arguments can be determined for calls as well as for + * declarations of functions or methods. For other cursors -1 is returned. + */ +// llgo:link (*Cursor).wrapNumArguments C.wrap_clang_Cursor_getNumArguments +func (*Cursor) wrapNumArguments() (num c.Int) { + return 0 +} + +func (c Cursor) NumArguments() (num c.Int) { + return c.wrapNumArguments() +} + +/** + * Retrieve the argument cursor of a function or method. + * + * The argument cursor can be determined for calls as well as for declarations + * of functions or methods. For other cursors and for invalid indices, an + * invalid cursor is returned. + */ +// llgo:link (*Cursor).wrapArgument C.wrap_clang_Cursor_getArgument +func (*Cursor) wrapArgument(index c.Uint, arg *Cursor) {} + +func (c Cursor) Argument(index c.Uint) (arg Cursor) { + c.wrapArgument(index, &arg) + return +} + +// llgo:link (*Cursor).wrapLocation C.wrap_clang_getCursorLocation +func (c *Cursor) wrapLocation(loc *SourceLocation) {} + +func (c Cursor) Location() (loc SourceLocation) { + c.wrapLocation(&loc) + return +} + +// llgo:link (*SourceLocation).wrapSpellingLocation C.wrap_clang_getSpellingLocation +func (l *SourceLocation) wrapSpellingLocation(file *File, line, column, offset *c.Uint) {} + +func (l SourceLocation) SpellingLocation(file *File, line, column, offset *c.Uint) { + l.wrapSpellingLocation(file, line, column, offset) +} + +/** + * Pretty-print the underlying type using the rules of the + * language of the translation unit from which it came. + * + * If the type is invalid, an empty string is returned. + */ +// llgo:link (*Type).wrapString C.wrap_clang_getTypeSpelling +func (t *Type) wrapString() (ret String) { + return +} + +func (t Type) String() (ret String) { + return t.wrapString() +} + +//llgo:link File.FileName C.clang_getFileName +func (File) FileName() (ret String) { return } + /** * Describes how the traversal of the children of a particular * cursor should proceed after visiting a particular child cursor. diff --git a/c/libuv/README.md b/c/libuv/README.md new file mode 100644 index 00000000..30f2ed5b --- /dev/null +++ b/c/libuv/README.md @@ -0,0 +1,44 @@ +LLGo wrapper of libuv +===== + +## How to install + +### on macOS (Homebrew) + +```sh +brew install libuv +``` + +### on Linux (Debian/Ubuntu) + +```sh +apt-get install -y libuv1-dev +``` + +### on Linux (CentOS/RHEL) + +```sh +yum install -y libuv-devel +``` + +### on Linux (Arch Linux) + +```sh +pacman -S libuv +``` + +## Demos + +The `_demo` directory contains our demos (it start with `_` to prevent the `go` command from compiling it): + +* [async_fs](_demo/async_fs/async_fs.go): a simple async file read demo +* [echo_server](_demo/echo_server/echo_server.go): a basic async tcp echo server + +### How to run demos + +To run the demos in directory `_demo`: + +```sh +cd # eg. cd _demo/sqlitedemo +llgo run . +``` diff --git a/c/libuv/_demo/async_fs/async_fs.go b/c/libuv/_demo/async_fs/async_fs.go new file mode 100644 index 00000000..14b50ecd --- /dev/null +++ b/c/libuv/_demo/async_fs/async_fs.go @@ -0,0 +1,116 @@ +package main + +import ( + "unsafe" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/libuv" + "github.com/goplus/llgo/c/os" +) + +const BUFFER_SIZE = 1024 + +var ( + loop *libuv.Loop + openReq libuv.Fs + closeReq libuv.Fs + + buffer [BUFFER_SIZE]c.Char + iov libuv.Buf + file libuv.File +) + +func main() { + // Print the libuv version + c.Printf(c.Str("libuv version: %d\n"), libuv.Version()) + + // Initialize the loop + loop = libuv.DefaultLoop() + + // Open the file + libuv.FsOpen(loop, &openReq, c.Str("example.txt"), os.O_RDONLY, 0, onOpen) + + // Run the loop + result := libuv.Run(loop, libuv.RUN_DEFAULT) + + if result != 0 { + c.Fprintf(c.Stderr, c.Str("Error in Run: %s\n"), libuv.Strerror(libuv.Errno(result))) + } + + // Cleanup + defer cleanup() +} + +func onOpen(req *libuv.Fs) { + // Check for errors + if libuv.FsGetResult(req) < 0 { + c.Fprintf(c.Stderr, c.Str("Error opening file: %s\n"), libuv.Strerror(libuv.Errno(libuv.FsGetResult(req)))) + libuv.LoopClose(loop) + return + } + + // Store the file descriptor + file = libuv.File(libuv.FsGetResult(req)) + + // Init buffer + iov = libuv.InitBuf((*c.Char)(unsafe.Pointer(&buffer[0])), c.Uint(unsafe.Sizeof(buffer))) + + // Read the file + readFile() + +} + +func readFile() { + // Initialize the request every time + var readReq libuv.Fs + + // Read the file + readRes := libuv.FsRead(loop, &readReq, file, &iov, 1, -1, onRead) + if readRes != 0 { + c.Printf(c.Str("Error in FsRead: %s (code: %d)\n"), libuv.Strerror(libuv.Errno(readRes)), readRes) + libuv.FsReqCleanup(&readReq) + libuv.LoopClose(loop) + } +} + +func onRead(req *libuv.Fs) { + // Cleanup the request + defer libuv.FsReqCleanup(req) + // Check for errors + if libuv.FsGetResult(req) < 0 { + c.Fprintf(c.Stderr, c.Str("Read error: %s\n"), libuv.Strerror(libuv.Errno(libuv.FsGetResult(req)))) + } else if libuv.FsGetResult(req) == 0 { + // Close the file + closeRes := libuv.FsClose(loop, &closeReq, libuv.File(libuv.FsGetResult(&openReq)), onClose) + if closeRes != 0 { + c.Printf(c.Str("Error in FsClose: %s (code: %d)\n"), libuv.Strerror(libuv.Errno(closeRes)), closeRes) + libuv.LoopClose(loop) + return + } + } else { + c.Printf(c.Str("Read %d bytes\n"), libuv.FsGetResult(req)) + c.Printf(c.Str("Read content: %.*s\n"), libuv.FsGetResult(req), (*c.Char)(unsafe.Pointer(&buffer[0]))) + // Read the file again + readFile() + } +} + +func onClose(req *libuv.Fs) { + // Check for errors + if libuv.FsGetResult(req) < 0 { + c.Fprintf(c.Stderr, c.Str("Error closing file: %s\n"), libuv.Strerror(libuv.Errno(libuv.FsGetResult(req)))) + } else { + c.Printf(c.Str("\nFile closed successfully.\n")) + } +} + +func cleanup() { + // Cleanup the requests + libuv.FsReqCleanup(&openReq) + libuv.FsReqCleanup(&closeReq) + // Close the loop + result := libuv.LoopClose(loop) + if result != 0 { + c.Fprintf(c.Stderr, c.Str("Error in LoopClose: %s\n"), libuv.Strerror(libuv.Errno(result))) + } +} diff --git a/c/libuv/_demo/async_fs/example.txt b/c/libuv/_demo/async_fs/example.txt new file mode 100755 index 00000000..190a1803 --- /dev/null +++ b/c/libuv/_demo/async_fs/example.txt @@ -0,0 +1 @@ +123 diff --git a/c/libuv/_demo/echo_server/echo_server.go b/c/libuv/_demo/echo_server/echo_server.go new file mode 100644 index 00000000..9d961200 --- /dev/null +++ b/c/libuv/_demo/echo_server/echo_server.go @@ -0,0 +1,118 @@ +package main + +import ( + "unsafe" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/libuv" + "github.com/goplus/llgo/c/net" +) + +var DEFAULT_PORT c.Int = 8080 +var DEFAULT_BACKLOG c.Int = 128 + +type WriteReq struct { + Req libuv.Write + Buf libuv.Buf +} + +func main() { + // Initialize the default event loop + var loop = libuv.DefaultLoop() + + // Initialize a TCP server + var server libuv.Tcp + libuv.InitTcp(loop, &server) + + // Set up the address to bind the server to + var addr net.SockaddrIn + libuv.Ip4Addr(c.Str("0.0.0.0"), DEFAULT_PORT, &addr) + c.Printf(c.Str("Listening on %s:%d\n"), c.Str("0.0.0.0"), DEFAULT_PORT) + + // Bind the server to the specified address and port + (&server).Bind((*net.SockAddr)(c.Pointer(&addr)), 0) + res := (*libuv.Stream)(&server).Listen(DEFAULT_BACKLOG, OnNewConnection) + if res != 0 { + c.Fprintf(c.Stderr, c.Str("Listen error: %s\n"), libuv.Strerror((libuv.Errno(res)))) + return + } + + // Start listening for incoming connections + libuv.Run(loop, libuv.RUN_DEFAULT) +} + +func FreeWriteReq(req *libuv.Write) { + wr := (*WriteReq)(c.Pointer(req)) + // Free the buffer base and the WriteReq itself. + c.Free(c.Pointer(wr.Buf.Base)) + c.Free(c.Pointer(wr)) +} + +func AllocBuffer(handle *libuv.Handle, suggestedSize uintptr, buf *libuv.Buf) { + // Allocate memory for the buffer based on the suggested size. + buf.Base = (*c.Char)(c.Malloc(suggestedSize)) + buf.Len = suggestedSize +} + +func EchoWrite(req *libuv.Write, status c.Int) { + if status != 0 { + c.Fprintf(c.Stderr, c.Str("Write error: %s\n"), libuv.Strerror((libuv.Errno(status)))) + } + FreeWriteReq(req) +} + +func EchoRead(client *libuv.Stream, nread c.Long, buf *libuv.Buf) { + if nread > 0 { + req := (*WriteReq)(c.Malloc(unsafe.Sizeof(WriteReq{}))) + if req == nil { + c.Fprintf(c.Stderr, c.Str("Failed to allocate memory for write request\n")) + c.Free(c.Pointer(buf.Base)) + return + } + // Initialize the buffer with the data read. + req.Buf = libuv.InitBuf(buf.Base, c.Uint(nread)) + // Write the data back to the client. + (&req.Req).Write(client, &req.Buf, 1, EchoWrite) + return + } + if nread < 0 { + // Handle read errors and EOF. + if (libuv.Errno)(nread) != libuv.EOF { + c.Fprintf(c.Stderr, c.Str("Read error: %s\n"), libuv.Strerror((libuv.Errno)(nread))) + } + (*libuv.Handle)(c.Pointer(client)).Close(nil) + } + // Free the buffer if it's no longer needed. + if buf.Base != nil { + c.Free(c.Pointer(buf.Base)) + } +} + +func OnNewConnection(server *libuv.Stream, status c.Int) { + if status < 0 { + c.Fprintf(c.Stderr, c.Str("New connection error: %s\n"), libuv.Strerror(libuv.Errno(status))) + return + } + + // Allocate memory for a new client. + client := (*libuv.Tcp)(c.Malloc(unsafe.Sizeof(libuv.Tcp{}))) + + if client == nil { + c.Fprintf(c.Stderr, c.Str("Failed to allocate memory for client\n")) + return + } + + // Initialize the client TCP handle. + if libuv.InitTcp(libuv.DefaultLoop(), client) < 0 { + c.Fprintf(c.Stderr, c.Str("Failed to initialize client\n")) + c.Free(c.Pointer(client)) + return + } + + // Accept the new connection and start reading data. + if server.Accept((*libuv.Stream)(client)) == 0 { + (*libuv.Stream)(client).StartRead(AllocBuffer, EchoRead) + } else { + (*libuv.Handle)(c.Pointer(client)).Close(nil) + } +} diff --git a/c/libuv/error.go b/c/libuv/error.go new file mode 100644 index 00000000..13f0029f --- /dev/null +++ b/c/libuv/error.go @@ -0,0 +1,118 @@ +package libuv + +import ( + _ "unsafe" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/net" + "github.com/goplus/llgo/c/syscall" +) + +const ( + E2BIG = Errno(syscall.E2BIG) + EACCES = Errno(syscall.EACCES) + EADDRINUSE = Errno(syscall.EADDRINUSE) + EADDRNOTAVAIL = Errno(syscall.EADDRNOTAVAIL) + EAFNOSUPPORT = Errno(syscall.EAFNOSUPPORT) + EAGAIN = Errno(syscall.EAGAIN) + EALREADY = Errno(syscall.EALREADY) + EBADF = Errno(syscall.EBADF) + EBUSY = Errno(syscall.EBUSY) + ECANCELED = Errno(syscall.ECANCELED) + ECONNABORTED = Errno(syscall.ECONNABORTED) + ECONNREFUSED = Errno(syscall.ECONNREFUSED) + ECONNRESET = Errno(syscall.ECONNRESET) + EDESTADDRREQ = Errno(syscall.EDESTADDRREQ) + EEXIST = Errno(syscall.EEXIST) + EFAULT = Errno(syscall.EFAULT) + EFBIG = Errno(syscall.EFBIG) + EHOSTUNREACH = Errno(syscall.EHOSTUNREACH) + EINTR = Errno(syscall.EINTR) + EINVAL = Errno(syscall.EINVAL) + EIO = Errno(syscall.EIO) + EISCONN = Errno(syscall.EISCONN) + EISDIR = Errno(syscall.EISDIR) + ELOOP = Errno(syscall.ELOOP) + EMFILE = Errno(syscall.EMFILE) + EMSGSIZE = Errno(syscall.EMSGSIZE) + ENAMETOOLONG = Errno(syscall.ENAMETOOLONG) + ENETDOWN = Errno(syscall.ENETDOWN) + ENETUNREACH = Errno(syscall.ENETUNREACH) + ENFILE = Errno(syscall.ENFILE) + ENOBUFS = Errno(syscall.ENOBUFS) + ENODEV = Errno(syscall.ENODEV) + ENOENT = Errno(syscall.ENOENT) + ENOMEM = Errno(syscall.ENOMEM) + ENOPROTOOPT = Errno(syscall.ENOPROTOOPT) + ENOSPC = Errno(syscall.ENOSPC) + ENOSYS = Errno(syscall.ENOSYS) + ENOTCONN = Errno(syscall.ENOTCONN) + ENOTDIR = Errno(syscall.ENOTDIR) + ENOTEMPTY = Errno(syscall.ENOTEMPTY) + ENOTSOCK = Errno(syscall.ENOTSOCK) + ENOTSUP = Errno(syscall.ENOTSUP) + EOVERFLOW = Errno(syscall.EOVERFLOW) + EPERM = Errno(syscall.EPERM) + EPIPE = Errno(syscall.EPIPE) + EPROTO = Errno(syscall.EPROTO) + EPROTONOSUPPORT = Errno(syscall.EPROTONOSUPPORT) + EPROTOTYPE = Errno(syscall.EPROTOTYPE) + ERANGE = Errno(syscall.ERANGE) + EROFS = Errno(syscall.EROFS) + ESHUTDOWN = Errno(syscall.ESHUTDOWN) + ESPIPE = Errno(syscall.ESPIPE) + ESRCH = Errno(syscall.ESRCH) + ETIMEDOUT = Errno(syscall.ETIMEDOUT) + ETXTBSY = Errno(syscall.ETXTBSY) + EXDEV = Errno(syscall.EXDEV) + ENXIO = Errno(syscall.ENXIO) + EMLINK = Errno(syscall.EMLINK) + EHOSTDOWN = Errno(syscall.EHOSTDOWN) + ENOTTY = Errno(syscall.ENOTTY) + //EFTYPE = Errno(syscall.EFTYPE) + EILSEQ = Errno(syscall.EILSEQ) + ESOCKTNOSUPPORT = Errno(syscall.ESOCKTNOSUPPORT) +) + +const ( + EAI_ADDRFAMILY = Errno(net.EAI_ADDRFAMILY) + EAI_AGAIN = Errno(net.EAI_AGAIN) + EAI_BADFLAGS = Errno(net.EAI_BADFLAGS) + EAI_BADHINTS = Errno(net.EAI_BADHINTS) + EAI_FAIL = Errno(net.EAI_FAIL) + EAI_FAMILY = Errno(net.EAI_FAMILY) + EAI_MEMORY = Errno(net.EAI_MEMORY) + EAI_NODATA = Errno(net.EAI_NODATA) + EAI_NONAME = Errno(net.EAI_NONAME) + EAI_OVERFLOW = Errno(net.EAI_OVERFLOW) + EAI_PROTOCOL = Errno(net.EAI_PROTOCOL) + EAI_SERVICE = Errno(net.EAI_SERVICE) + EAI_SOCKTYPE = Errno(net.EAI_SOCKTYPE) +) + +const ( + EAI_CANCELED Errno = -3003 + ECHARSET Errno = -4080 + ENONET Errno = -4056 + UNKNOWN Errno = -4094 + EOF Errno = -1 + EREMOTEIO Errno = -4030 + ERRNO_MAX Errno = EOF - 1 +) + +type Errno c.Int + +//go:linkname TranslateSysError C.uv_translate_sys_error +func TranslateSysError(sysErrno c.Int) Errno + +//go:linkname Strerror C.uv_strerror +func Strerror(err Errno) *c.Char + +//go:linkname StrerrorR C.uv_strerror_r +func StrerrorR(err Errno, buf *c.Char, bufLen uintptr) *c.Char + +//go:linkname ErrName C.uv_err_name +func ErrName(err Errno) *c.Char + +//go:linkname ErrNameR C.uv_err_name_r +func ErrNameR(err Errno, buf *c.Char, bufLen uintptr) *c.Char diff --git a/c/libuv/fs.go b/c/libuv/fs.go new file mode 100644 index 00000000..9bb8f57f --- /dev/null +++ b/c/libuv/fs.go @@ -0,0 +1,272 @@ +package libuv + +import ( + _ "unsafe" + + "github.com/goplus/llgo/c" +) + +const ( + FS_UNKNOWN FsType = -1 + FS_CUSTOM FsType = 0 + FS_OPEN FsType = 1 + FS_CLOSE FsType = 2 + FS_READ FsType = 3 + FS_WRITE FsType = 4 + FS_SENDFILE FsType = 5 + FS_STAT FsType = 6 + FS_LSTAT FsType = 7 + FS_FSTAT FsType = 8 + FS_FTRUNCATE FsType = 9 + FS_UTIME FsType = 10 + FS_FUTIME FsType = 11 + FS_ACCESS FsType = 12 + FS_CHMOD FsType = 13 + FS_FCHMOD FsType = 14 + FS_FSYNC FsType = 15 + FS_FDATASYNC FsType = 16 + FS_UNLINK FsType = 17 + FS_RMDIR FsType = 18 + FS_MKDIR FsType = 19 + FS_MKDTEMP FsType = 20 + FS_RENAME FsType = 21 + FS_SCANDIR FsType = 22 + FS_LINK FsType = 23 + FS_SYMLINK FsType = 24 + FS_READLINK FsType = 25 + FS_CHOWN FsType = 26 + FS_FCHOWN FsType = 27 + FS_REALPATH FsType = 28 + FS_COPYFILE FsType = 29 + FS_LCHOWN FsType = 30 + FS_OPENDIR FsType = 31 + FS_READDIR FsType = 32 + FS_CLOSEDIR FsType = 33 + FS_STATFS FsType = 34 + FS_MKSTEMP FsType = 35 + FS_LUTIME FsType = 36 +) + +const ( + DirentUnknown DirentType = iota + DirentFile + DirentDir + DirentLink + DirentFifo + DirentSocket + DirentChar + DirentBlock +) + +type FsType c.Int + +type DirentType c.Int + +type File c.Int + +// ---------------------------------------------- + +/* Handle types. */ + +type Fs struct { + Unused [440]byte +} + +type FsEvent struct { + Unused [0]byte +} + +type FsPoll struct { + Unused [0]byte +} + +type Dirent struct { + Name *c.Char + Type DirentType +} + +type Stat struct { + Unused [0]byte +} + +// ---------------------------------------------- + +/* Function type */ + +// llgo:type C +type FsCb func(req *Fs) + +// llgo:type C +type FsEventCb func(handle *FsEvent, filename *c.Char, events c.Int, status c.Int) + +// llgo:type C +type FsPollCb func(handle *FsPoll, status c.Int, events c.Int) + +// ---------------------------------------------- + +/* Fs related function and method */ + +//go:linkname FsGetType C.uv_fs_get_type +func FsGetType(req *Fs) FsType + +//go:linkname FsGetPath C.uv_fs_get_path +func FsGetPath(req *Fs) *c.Char + +//go:linkname FsGetResult C.uv_fs_get_result +func FsGetResult(req *Fs) c.Int + +//go:linkname FsGetPtr C.uv_fs_get_ptr +func FsGetPtr(req *Fs) c.Pointer + +//go:linkname FsGetSystemError C.uv_fs_get_system_error +func FsGetSystemError(req *Fs) c.Int + +//go:linkname FsGetStatBuf C.uv_fs_get_statbuf +func FsGetStatBuf(req *Fs) *Stat + +//go:linkname FsReqCleanup C.uv_fs_req_cleanup +func FsReqCleanup(req *Fs) + +//go:linkname DefaultLoop C.uv_default_loop +func DefaultLoop() *Loop + +//go:linkname FsOpen C.uv_fs_open +func FsOpen(loop *Loop, req *Fs, path *c.Char, flags c.Int, mode c.Int, cb FsCb) c.Int + +//go:linkname FsClose C.uv_fs_close +func FsClose(loop *Loop, req *Fs, file File, cb FsCb) c.Int + +//go:linkname FsRead C.uv_fs_read +func FsRead(loop *Loop, req *Fs, file File, bufs *Buf, nbufs c.Uint, offset c.LongLong, cb FsCb) c.Int + +//go:linkname FsWrite C.uv_fs_write +func FsWrite(loop *Loop, req *Fs, file File, bufs *Buf, nbufs c.Uint, offset c.LongLong, cb FsCb) c.Int + +//go:linkname FsUnlink C.uv_fs_unlink +func FsUnlink(loop *Loop, req *Fs, path *c.Char, cb FsCb) c.Int + +//go:linkname FsMkdir C.uv_fs_mkdir +func FsMkdir(loop *Loop, req *Fs, path *c.Char, mode c.Int, cb FsCb) c.Int + +//go:linkname FsMkdtemp C.uv_fs_mkdtemp +func FsMkdtemp(loop *Loop, req *Fs, tpl *c.Char, cb FsCb) c.Int + +//go:linkname FsMkStemp C.uv_fs_mkstemp +func FsMkStemp(loop *Loop, req *Fs, tpl *c.Char, cb FsCb) c.Int + +//go:linkname FsRmdir C.uv_fs_rmdir +func FsRmdir(loop *Loop, req *Fs, path *c.Char, cb FsCb) c.Int + +//go:linkname FsStat C.uv_fs_stat +func FsStat(loop *Loop, req *Fs, path *c.Char, cb FsCb) c.Int + +//go:linkname FsFstat C.uv_fs_fstat +func FsFstat(loop *Loop, req *Fs, file File, cb FsCb) c.Int + +//go:linkname FsRename C.uv_fs_rename +func FsRename(loop *Loop, req *Fs, path *c.Char, newPath *c.Char, cb FsCb) c.Int + +//go:linkname FsFsync C.uv_fs_fsync +func FsFsync(loop *Loop, req *Fs, file File, cb FsCb) c.Int + +//go:linkname FsFdatasync C.uv_fs_fdatasync +func FsFdatasync(loop *Loop, req *Fs, file File, cb FsCb) c.Int + +//go:linkname FsFtruncate C.uv_fs_ftruncate +func FsFtruncate(loop *Loop, req *Fs, file File, offset c.LongLong, cb FsCb) c.Int + +//go:linkname FsSendfile C.uv_fs_sendfile +func FsSendfile(loop *Loop, req *Fs, outFd c.Int, inFd c.Int, inOffset c.LongLong, length c.Int, cb FsCb) c.Int + +//go:linkname FsAccess C.uv_fs_access +func FsAccess(loop *Loop, req *Fs, path *c.Char, flags c.Int, cb FsCb) c.Int + +//go:linkname FsChmod C.uv_fs_chmod +func FsChmod(loop *Loop, req *Fs, path *c.Char, mode c.Int, cb FsCb) c.Int + +//go:linkname FsFchmod C.uv_fs_fchmod +func FsFchmod(loop *Loop, req *Fs, file File, mode c.Int, cb FsCb) c.Int + +//go:linkname FsUtime C.uv_fs_utime +func FsUtime(loop *Loop, req *Fs, path *c.Char, atime c.Int, mtime c.Int, cb FsCb) c.Int + +//go:linkname FsFutime C.uv_fs_futime +func FsFutime(loop *Loop, req *Fs, file File, atime c.Int, mtime c.Int, cb FsCb) c.Int + +//go:linkname FsLutime C.uv_fs_lutime +func FsLutime(loop *Loop, req *Fs, path *c.Char, atime c.Int, mtime c.Int, cb FsCb) c.Int + +//go:linkname FsLink C.uv_fs_link +func FsLink(loop *Loop, req *Fs, path *c.Char, newPath *c.Char, cb FsCb) c.Int + +//go:linkname FsSymlink C.uv_fs_symlink +func FsSymlink(loop *Loop, req *Fs, path *c.Char, newPath *c.Char, flags c.Int, cb FsCb) c.Int + +//go:linkname FsReadlink C.uv_fs_read +func FsReadlink(loop *Loop, req *Fs, path *c.Char, cb FsCb) c.Int + +//go:linkname FsRealpath C.uv_fs_realpath +func FsRealpath(loop *Loop, req *Fs, path *c.Char, cb FsCb) c.Int + +//go:linkname FsCopyfile C.uv_fs_copyfile +func FsCopyfile(loop *Loop, req *Fs, path *c.Char, newPath *c.Char, flags c.Int, cb FsCb) c.Int + +//go:linkname FsScandir C.uv_fs_scandir +func FsScandir(loop *Loop, req *Fs, path *c.Char, flags c.Int, cb FsCb) c.Int + +//go:linkname FsScandirNext C.uv_fs_scandir_next +func FsScandirNext(req *Fs, ent *Dirent) c.Int + +//go:linkname FsOpenDir C.uv_fs_opendir +func FsOpenDir(loop *Loop, req *Fs, path *c.Char, cb FsCb) c.Int + +//go:linkname FsReaddir C.uv_fs_readdir +func FsReaddir(loop *Loop, req *Fs, dir c.Int, cb FsCb) c.Int + +//go:linkname FsCloseDir C.uv_fs_closedir +func FsCloseDir(loop *Loop, req *Fs) c.Int + +//go:linkname FsStatfs C.uv_fs_statfs +func FsStatfs(loop *Loop, req *Fs, path *c.Char, cb FsCb) c.Int + +//go:linkname FsChown C.uv_fs_chown +func FsChown(loop *Loop, req *Fs, path *c.Char, uid c.Int, gid c.Int, cb FsCb) c.Int + +//go:linkname FsFchown C.uv_fs_fchown +func FsFchown(loop *Loop, req *Fs, file File, uid c.Int, gid c.Int, cb FsCb) c.Int + +//go:linkname FsLchown C.uv_fs_lchown +func FsLchown(loop *Loop, req *Fs, path *c.Char, uid c.Int, gid c.Int, cb FsCb) c.Int + +//go:linkname FsLstat C.uv_fs_lstat +func FsLstat(loop *Loop, req *Fs, path *c.Char, cb FsCb) c.Int + +//go:linkname FsEventInit C.uv_fs_event_init +func FsEventInit(loop *Loop, handle *FsEvent) c.Int + +//go:linkname FsEventStart C.uv_fs_event_start +func FsEventStart(handle *FsEvent, cb FsEventCb, path *c.Char, flags c.Int) c.Int + +//go:linkname FsEventStop C.uv_fs_event_stop +func FsEventStop(handle *FsEvent) c.Int + +//go:linkname FsEventClose C.uv_fs_event_close +func FsEventClose(handle *FsEvent) c.Int + +//go:linkname FsEventGetpath C.uv_fs_event_getpath +func FsEventGetpath(handle *FsEvent) *c.Char + +//go:linkname FsPollInit C.uv_fs_poll_init +func FsPollInit(loop *Loop, handle *FsPoll) c.Int + +//go:linkname FsPollStart C.uv_fs_poll_start +func FsPollStart(handle *FsPoll, cb FsPollCb, path *c.Char, interval uint) c.Int + +//go:linkname FsPollStop C.uv_fs_poll_stop +func FsPollStop(handle *FsPoll) c.Int + +//go:linkname FsPollClose C.uv_fs_poll_close +func FsPollClose(handle *FsPoll) c.Int + +//go:linkname FsPollGetPath C.uv_fs_poll_getpath +func FsPollGetPath(handle *FsPoll) *c.Char diff --git a/c/libuv/libuv.go b/c/libuv/libuv.go new file mode 100644 index 00000000..b567bedc --- /dev/null +++ b/c/libuv/libuv.go @@ -0,0 +1,238 @@ +package libuv + +import ( + _ "unsafe" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/net" +) + +const ( + LLGoPackage = "link: $(pkg-config --libs libuv); -luv" +) + +// ---------------------------------------------- +const ( + RUN_DEFAULT RunMode = iota + RUN_ONCE + RUN_NOWAIT +) + +const ( + LOOP_BLOCK_SIGNAL LoopOption = iota + METRICS_IDLE_TIME +) + +const ( + UV_LEAVE_GROUP Membership = iota + UV_JOIN_GROUP +) + +const ( + UNKNOWN_HANDLE HandleType = iota + ASYNC + CHECK + FS_EVENT + FS_POLL + HANDLE + IDLE + NAMED_PIPE + POLL + PREPARE + PROCESS + STREAM + TCP + TIMER + TTY + UDP + SIGNAL + FILE + HANDLE_TYPE_MAX +) + +const ( + UNKNOWN_REQ ReqType = iota + REQ + CONNECT + WRITE + SHUTDOWN + UDP_SEND + FS + WORK + GETADDRINFO + GETNAMEINFO + RANDOM + REQ_TYPE_PRIVATE + REQ_TYPE_MAX +) + +const ( + READABLE PollEvent = 1 << iota + WRITABLE + DISCONNECT + PRIPRIORITIZED +) + +type RunMode c.Int + +type LoopOption c.Int + +type Membership c.Int + +type HandleType c.Int + +type ReqType c.Int + +type OsSock c.Int + +type OsFd c.Int + +type PollEvent c.Int + +// ---------------------------------------------- + +/* Handle types. */ + +type Loop struct { + Unused [0]byte +} + +type Poll struct { + Unused [0]byte +} + +/* Request types. */ + +type Shutdown struct { + Unused [0]byte +} + +type Buf struct { + Base *c.Char + Len uintptr +} // ---------------------------------------------- + +/* Function type */ + +// llgo:type C +type MallocFunc func(size uintptr) c.Pointer + +// llgo:type C +type ReallocFunc func(ptr c.Pointer, size uintptr) c.Pointer + +// llgo:type C +type CallocFunc func(count uintptr, size uintptr) c.Pointer + +// llgo:type C +type FreeFunc func(ptr c.Pointer) + +// llgo:type C +type AllocCb func(handle *Handle, suggestedSize uintptr, buf *Buf) + +// llgo:type C +type GetaddrinfoCb func(req *GetAddrInfo, status c.Int, res *net.AddrInfo) + +// llgo:type C +type GetnameinfoCb func(req *GetNameInfo, status c.Int, hostname *c.Char, service *c.Char) + +// llgo:type C +type ShutdownCb func(req *Shutdown, status c.Int) + +// llgo:type C +type WalkCb func(handle *Handle, arg c.Pointer) + +// llgo:type C +type PollCb func(handle *Poll, status c.Int, events c.Int) + +// ---------------------------------------------- + +//go:linkname Version C.uv_version +func Version() c.Uint + +//go:linkname VersionString C.uv_version_string +func VersionString() *c.Char + +//go:linkname LibraryShutdown C.uv_library_shutdown +func LibraryShutdown() + +//go:linkname ReplaceAllocator C.uv_replace_allocator +func ReplaceAllocator(mallocFunc MallocFunc, reallocFunc ReallocFunc, callocFunc CallocFunc, freeFunc FreeFunc) c.Int + +// ---------------------------------------------- + +// llgo:link (*Shutdown).Shutdown C.uv_shutdown +func (shutdown *Shutdown) Shutdown(stream *Stream, shutdownCb ShutdownCb) c.Int { + return 0 +} + +// ---------------------------------------------- + +/* Loop related functions and method. */ + +//go:linkname LoopSize C.uv_loop_size +func LoopSize() uintptr + +//go:linkname Run C.uv_run +func Run(loop *Loop, mode RunMode) c.Int + +//go:linkname LoopAlive C.uv_loop_alive +func LoopAlive(loop *Loop) c.Int + +//go:linkname LoopClose C.uv_loop_close +func LoopClose(loop *Loop) c.Int + +//go:linkname LoopConfigure C.uv_loop_configure +func LoopConfigure(loop *Loop, option LoopOption, arg c.Int) c.Int + +//go:linkname LoopDefault C.uv_default_loop +func LoopDefault() *Loop + +//go:linkname LoopDelete C.uv_loop_delete +func LoopDelete(loop *Loop) c.Int + +//go:linkname LoopFork C.uv_loop_fork +func LoopFork(loop *Loop) c.Int + +//go:linkname LoopInit C.uv_loop_init +func LoopInit(loop *Loop) c.Int + +//go:linkname LoopNew C.uv_loop_new +func LoopNew() *Loop + +//go:linkname LoopNow C.uv_now +func LoopNow(loop *Loop) c.UlongLong + +//go:linkname LoopUpdateTime C.uv_update_time +func LoopUpdateTime(loop *Loop) + +//go:linkname LoopBackendFd C.uv_backend_fd +func LoopBackendFd(loop *Loop) c.Int + +//go:linkname LoopBackendTimeout C.uv_backend_timeout +func LoopBackendTimeout(loop *Loop) c.Int + +//go:linkname LoopWalk C.uv_walk +func LoopWalk(loop *Loop, walkCb WalkCb, arg c.Pointer) + +// ---------------------------------------------- + +/* Buf related functions and method. */ + +//go:linkname InitBuf C.uv_buf_init +func InitBuf(base *c.Char, len c.Uint) Buf + +// ---------------------------------------------- + +/* Poll related function and method */ + +//go:linkname PollInit C.uv_poll_init +func PollInit(loop *Loop, handle *Poll, fd OsFd) c.Int + +//go:linkname PollStart C.uv_poll_start +func PollStart(handle *Poll, events c.Int, cb PollCb) c.Int + +//go:linkname PollStop C.uv_poll_stop +func PollStop(handle *Poll) c.Int + +//go:linkname PollInitSocket C.uv_poll_init_socket +func PollInitSocket(loop *Loop, handle *Poll, socket c.Int) c.Int diff --git a/c/libuv/net.go b/c/libuv/net.go new file mode 100644 index 00000000..2463666c --- /dev/null +++ b/c/libuv/net.go @@ -0,0 +1,503 @@ +package libuv + +import ( + _ "unsafe" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/net" +) + +const ( + /* Used with uv_tcp_bind, when an IPv6 address is used. */ + TCP_IPV6ONLY TcpFlags = 1 +) + +/* + * UDP support. + */ +const ( + /* Disables dual stack mode. */ + UDP_IPV6ONLY UdpFlags = 1 + /* + * Indicates message was truncated because read buffer was too small. The + * remainder was discarded by the OS. Used in uv_udp_recv_cb. + */ + UDP_PARTIAL UdpFlags = 2 + /* + * Indicates if SO_REUSEADDR will be set when binding the handle. + * This sets the SO_REUSEPORT socket flag on the BSDs and OS X. On other + * Unix platforms, it sets the SO_REUSEADDR flag. What that means is that + * multiple threads or processes can bind to the same address without error + * (provided they all set the flag) but only the last one to bind will receive + * any traffic, in effect "stealing" the port from the previous listener. + */ + UDP_REUSEADDR UdpFlags = 4 + /* + * Indicates that the message was received by recvmmsg, so the buffer provided + * must not be freed by the recv_cb callback. + */ + UDP_MMSG_CHUNK UdpFlags = 8 + /* + * Indicates that the buffer provided has been fully utilized by recvmmsg and + * that it should now be freed by the recv_cb callback. When this flag is set + * in uv_udp_recv_cb, nread will always be 0 and addr will always be NULL. + */ + UDP_MMSG_FREE UdpFlags = 16 + /* + * Indicates if IP_RECVERR/IPV6_RECVERR will be set when binding the handle. + * This sets IP_RECVERR for IPv4 and IPV6_RECVERR for IPv6 UDP sockets on + * Linux. This stops the Linux kernel from suppressing some ICMP error + * messages and enables full ICMP error reporting for faster failover. + * This flag is no-op on platforms other than Linux. + */ + UDP_LINUX_RECVERR UdpFlags = 32 + /* + * Indicates that recvmmsg should be used, if available. + */ + UDP_RECVMMSG UdpFlags = 256 +) + +type TcpFlags c.Int + +type UdpFlags c.Int + +// ---------------------------------------------- + +/* Handle types. */ + +// TODO(spongehah): Handle +type Handle struct { + Data c.Pointer + Unused [88]byte +} + +// TODO(spongehah): Stream +type Stream struct { + Data c.Pointer + Unused [256]byte +} + +// TODO(spongehah): Tcp +type Tcp struct { + Data c.Pointer + Unused [256]byte +} + +type Udp struct { + Unused [0]byte +} + +/* Request types. */ + +type Req struct { + Unused [0]byte +} + +type UdpSend struct { + Unused [0]byte +} + +// TODO(spongehah): Write +type Write struct { + Data c.Pointer + Unused [184]byte +} + +// TODO(spongehah): Connect +type Connect struct { + Data c.Pointer + Unused [88]byte +} + +type GetAddrInfo struct { + Unused [0]byte +} + +type GetNameInfo struct { + Unused [0]byte +} + +// ---------------------------------------------- + +/* Function type */ + +// llgo:type C +type CloseCb func(handle *Handle) + +// llgo:type C +type ConnectCb func(req *Connect, status c.Int) + +// llgo:type C +type UdpSendCb func(req *UdpSend, status c.Int) + +// llgo:type C +type UdpRecvCb func(handle *Udp, nread c.Long, buf *Buf, addr *net.SockAddr, flags c.Uint) + +// llgo:type C +type ReadCb func(stream *Stream, nread c.Long, buf *Buf) + +// llgo:type C +type WriteCb func(req *Write, status c.Int) + +// llgo:type C +type ConnectionCb func(server *Stream, status c.Int) + +// ---------------------------------------------- + +/* Handle related function and method */ + +// llgo:link (*Handle).Ref C.uv_ref +func (handle *Handle) Ref() {} + +// llgo:link (*Handle).Unref C.uv_unref +func (handle *Handle) Unref() {} + +// llgo:link (*Handle).HasRef C.uv_has_ref +func (handle *Handle) HasRef() c.Int { + return 0 +} + +//go:linkname HandleSize C.uv_handle_size +func HandleSize(handleType HandleType) uintptr + +// llgo:link (*Handle).GetType C.uv_handle_get_type +func (handle *Handle) GetType() HandleType { + return 0 +} + +//go:linkname HandleTypeName C.uv_handle_type_name +func HandleTypeName(handleType HandleType) *c.Char + +// llgo:link (*Handle).GetData C.uv_handle_get_data +func (handle *Handle) GetData() c.Pointer { + return nil +} + +// llgo:link (*Handle).GetLoop C.uv_handle_get_loop +func (handle *Handle) GetLoop() *Loop { + return nil +} + +// llgo:link (*Handle).SetData C.uv_handle_set_data +func (handle *Handle) SetData(data c.Pointer) {} + +// llgo:link (*Handle).IsActive C.uv_is_active +func (handle *Handle) IsActive() c.Int { + return 0 +} + +// llgo:link (*Handle).Close C.uv_close +func (handle *Handle) Close(closeCb CloseCb) {} + +// llgo:link (*Handle).SendBufferSize C.uv_send_buffer_size +func (handle *Handle) SendBufferSize(value *c.Int) c.Int { + return 0 +} + +// llgo:link (*Handle).RecvBufferSize C.uv_recv_buffer_size +func (handle *Handle) RecvBufferSize(value *c.Int) c.Int { + return 0 +} + +// llgo:link (*Handle).Fileno C.uv_fileno +func (handle *Handle) Fileno(fd *OsFd) c.Int { + return 0 +} + +//go:linkname Pipe C.uv_pipe +func Pipe(fds [2]File, readFlags c.Int, writeFlags c.Int) c.Int { + return 0 +} + +//go:linkname Socketpair C.uv_socketpair +func Socketpair(_type c.Int, protocol c.Int, socketVector [2]OsSock, flag0 c.Int, flag1 c.Int) c.Int { + return 0 +} + +// llgo:link (*Handle).IsClosing C.uv_is_closing +func (handle *Handle) IsClosing() c.Int { + return 0 +} + +// ---------------------------------------------- + +/* Req related function and method */ + +//go:linkname ReqSize C.uv_req_size +func ReqSize(reqType ReqType) uintptr + +// llgo:link (*Req).GetData C.uv_req_get_data +func (req *Req) GetData() c.Pointer { + return nil +} + +// llgo:link (*Req).SetData C.uv_handle_set_data +func (req *Req) SetData(data c.Pointer) {} + +// llgo:link (*Req).GetType C.uv_req_get_type +func (req *Req) GetType() ReqType { + return 0 +} + +//go:linkname TypeName C.uv_req_type_name +func TypeName(reqType ReqType) *c.Char + +// ---------------------------------------------- + +/* Stream related function and method */ + +// llgo:link (*Stream).GetWriteQueueSize C.uv_stream_get_write_queue_size +func (stream *Stream) GetWriteQueueSize() uintptr { + return 0 +} + +// llgo:link (*Stream).Listen C.uv_listen +func (stream *Stream) Listen(backlog c.Int, connectionCb ConnectionCb) c.Int { + return 0 +} + +// llgo:link (*Stream).Accept C.uv_accept +func (server *Stream) Accept(client *Stream) c.Int { + return 0 +} + +// llgo:link (*Stream).StartRead C.uv_read_start +func (stream *Stream) StartRead(allocCb AllocCb, readCb ReadCb) c.Int { + return 0 +} + +// llgo:link (*Stream).StopRead C.uv_read_stop +func (stream *Stream) StopRead() c.Int { + return 0 +} + +// llgo:link (*Write).Write C.uv_write +func (req *Write) Write(stream *Stream, bufs *Buf, nbufs c.Uint, writeCb WriteCb) c.Int { + return 0 +} + +// llgo:link (*Write).Write2 C.uv_write2 +func (req *Write) Write2(stream *Stream, bufs *Buf, nbufs c.Uint, sendStream *Stream, writeCb WriteCb) c.Int { + return 0 +} + +// llgo:link (*Stream).TryWrite C.uv_try_write +func (stream *Stream) TryWrite(bufs *Buf, nbufs c.Uint) c.Int { + return 0 +} + +// llgo:link (*Stream).TryWrite2 C.uv_try_write2 +func (stream *Stream) TryWrite2(bufs *Buf, nbufs c.Uint, sendStream *Stream) c.Int { + return 0 +} + +// llgo:link (*Stream).IsReadable C.uv_is_readable +func (stream *Stream) IsReadable() c.Int { + return 0 +} + +// llgo:link (*Stream).IsWritable C.uv_is_writable +func (stream *Stream) IsWritable() c.Int { + return 0 +} + +// llgo:link (*Stream).SetBlocking C.uv_stream_set_blocking +func (stream *Stream) SetBlocking(blocking c.Int) c.Int { + return 0 +} + +// ---------------------------------------------- + +/* Tcp related function and method */ + +//go:linkname InitTcp C.uv_tcp_init +func InitTcp(loop *Loop, tcp *Tcp) c.Int + +//go:linkname InitTcpEx C.uv_tcp_init_ex +func InitTcpEx(loop *Loop, tcp *Tcp, flags c.Uint) c.Int + +// llgo:link (*Tcp).Open C.uv_tcp_open +func (tcp *Tcp) Open(sock OsSock) c.Int { + return 0 +} + +// llgo:link (*Tcp).Nodelay C.uv_tcp_nodelay +func (tcp *Tcp) Nodelay(enable c.Int) c.Int { + return 0 +} + +// llgo:link (*Tcp).KeepAlive C.uv_tcp_keepalive +func (tcp *Tcp) KeepAlive(enable c.Int, delay c.Uint) c.Int { + return 0 +} + +// llgo:link (*Tcp).SimultaneousAccepts C.uv_tcp_simultaneous_accepts +func (tcp *Tcp) SimultaneousAccepts(enable c.Int) c.Int { + return 0 +} + +// llgo:link (*Tcp).Bind C.uv_tcp_bind +func (tcp *Tcp) Bind(addr *net.SockAddr, flags c.Uint) c.Int { + return 0 +} + +// llgo:link (*Tcp).Getsockname C.uv_tcp_getsockname +func (tcp *Tcp) Getsockname(name *net.SockAddr, nameLen *c.Int) c.Int { + return 0 +} + +// llgo:link (*Tcp).Getpeername C.uv_tcp_getpeername +func (tcp *Tcp) Getpeername(name *net.SockAddr, nameLen *c.Int) c.Int { + return 0 +} + +// llgo:link (*Tcp).CloseReset C.uv_tcp_close_reset +func (tcp *Tcp) CloseReset(closeCb CloseCb) c.Int { + return 0 +} + +//go:linkname TcpConnect C.uv_tcp_connect +func TcpConnect(req *Connect, tcp *Tcp, addr *net.SockAddr, connectCb ConnectCb) c.Int + +// ---------------------------------------------- + +/* Udp related function and method */ + +//go:linkname InitUdp C.uv_udp_init +func InitUdp(loop *Loop, udp *Udp) c.Int + +//go:linkname InitUdpEx C.uv_udp_init_ex +func InitUdpEx(loop *Loop, udp *Udp, flags c.Uint) c.Int + +// llgo:link (*Udp).Open C.uv_udp_open +func (udp *Udp) Open(sock OsSock) c.Int { + return 0 +} + +// llgo:link (*Udp).Bind C.uv_udp_bind +func (udp *Udp) Bind(addr *net.SockAddr, flags c.Uint) c.Int { + return 0 +} + +// llgo:link (*Udp).Connect C.uv_udp_connect +func (udp *Udp) Connect(addr *net.SockAddr) c.Int { + return 0 +} + +// llgo:link (*Udp).Getpeername C.uv_udp_getpeername +func (udp *Udp) Getpeername(name *net.SockAddr, nameLen *c.Int) c.Int { + return 0 +} + +// llgo:link (*Udp).Getsockname C.uv_udp_getsockname +func (udp *Udp) Getsockname(name *net.SockAddr, nameLen *c.Int) c.Int { + return 0 +} + +// llgo:link (*Udp).SetMembership C.uv_udp_set_membership +func (udp *Udp) SetMembership(multicastAddr *c.Char, interfaceAddr *c.Char, membership Membership) c.Int { + return 0 +} + +// llgo:link (*Udp).SourceMembership C.uv_udp_set_source_membership +func (udp *Udp) SourceMembership(multicastAddr *c.Char, interfaceAddr *c.Char, sourceAddr *c.Char, membership Membership) c.Int { + return 0 +} + +// llgo:link (*Udp).SetMulticastLoop C.uv_udp_set_multicast_loop +func (udp *Udp) SetMulticastLoop(on c.Int) c.Int { + return 0 +} + +// llgo:link (*Udp).SetMulticastTTL C.uv_udp_set_multicast_ttl +func (udp *Udp) SetMulticastTTL(ttl c.Int) c.Int { + return 0 +} + +// llgo:link (*Udp).SetMulticastInterface C.uv_udp_set_multicast_interface +func (udp *Udp) SetMulticastInterface(interfaceAddr *c.Char) c.Int { + return 0 +} + +// llgo:link (*Udp).SetBroadcast C.uv_udp_set_broadcast +func (udp *Udp) SetBroadcast(on c.Int) c.Int { + return 0 +} + +// llgo:link (*Udp).SetTTL C.uv_udp_set_ttl +func (udp *Udp) SetTTL(ttl c.Int) c.Int { + return 0 +} + +//go:linkname Send C.uv_udp_send +func Send(req *UdpSend, udp *Udp, bufs *Buf, nbufs c.Uint, addr *net.SockAddr, sendCb UdpSendCb) c.Int + +// llgo:link (*Udp).TrySend C.uv_udp_try_send +func (udp *Udp) TrySend(bufs *Buf, nbufs c.Uint, addr *net.SockAddr) c.Int { + return 0 +} + +// llgo:link (*Udp).StartRecv C.uv_udp_recv_start +func (udp *Udp) StartRecv(allocCb AllocCb, recvCb UdpRecvCb) c.Int { + return 0 +} + +// llgo:link (*Udp).UsingRecvmmsg C.uv_udp_using_recvmmsg +func (udp *Udp) UsingRecvmmsg() c.Int { + return 0 +} + +// llgo:link (*Udp).StopRecv C.uv_udp_recv_stop +func (udp *Udp) StopRecv() c.Int { + return 0 +} + +// llgo:link (*Udp).GetSendQueueSize C.uv_udp_get_send_queue_size +func (udp *Udp) GetSendQueueSize() uintptr { + return 0 +} + +// llgo:link (*Udp).GetSendQueueCount C.uv_udp_get_send_queue_count +func (udp *Udp) GetSendQueueCount() uintptr { + return 0 +} + +// ---------------------------------------------- + +//go:linkname Ip4Addr C.uv_ip4_addr +func Ip4Addr(ip *c.Char, port c.Int, addr *net.SockaddrIn) c.Int + +//go:linkname Ip6Addr C.uv_ip6_addr +func Ip6Addr(ip *c.Char, port c.Int, addr *net.SockaddrIn6) c.Int + +//go:linkname Ip4Name C.uv_ip4_name +func Ip4Name(src *net.SockaddrIn, dst *c.Char, size uintptr) c.Int + +//go:linkname Ip6Name C.uv_ip6_name +func Ip6Name(src *net.SockaddrIn6, dst *c.Char, size uintptr) c.Int + +//go:linkname IpName C.uv_ip_name +func IpName(src *net.SockAddr, dst *c.Char, size uintptr) c.Int + +//go:linkname InetNtop C.uv_inet_ntop +func InetNtop(af c.Int, src c.Pointer, dst *c.Char, size uintptr) c.Int + +//go:linkname InetPton C.uv_inet_pton +func InetPton(af c.Int, src *c.Char, dst c.Pointer) c.Int + +// ---------------------------------------------- + +/* Getaddrinfo related function and method */ + +//go:linkname Getaddrinfo C.uv_getaddrinfo +func Getaddrinfo(loop *Loop, req *GetAddrInfo, getaddrinfoCb GetaddrinfoCb, node *c.Char, service *c.Char, hints *net.AddrInfo) c.Int + +//go:linkname Freeaddrinfo C.uv_freeaddrinfo +func Freeaddrinfo(addrInfo *net.AddrInfo) + +// ---------------------------------------------- + +/* Getnameinfo related function and method */ + +//go:linkname Getnameinfo C.uv_getnameinfo +func Getnameinfo(loop *Loop, req *GetNameInfo, getnameinfoCb GetnameinfoCb, addr *net.SockAddr, flags c.Int) c.Int diff --git a/c/libuv/signal.go b/c/libuv/signal.go new file mode 100644 index 00000000..a3ede8ff --- /dev/null +++ b/c/libuv/signal.go @@ -0,0 +1,33 @@ +package libuv + +import ( + _ "unsafe" + + "github.com/goplus/llgo/c" +) + +/* Handle types. */ + +type Signal struct { + Unused [0]byte +} + +// ---------------------------------------------- + +/* Function type */ + +// llgo:type C +type SignalCb func(handle *Signal, sigNum c.Int) + +// ---------------------------------------------- + +/* Signal related functions and method. */ + +//go:linkname SignalInit C.uv_signal_init +func SignalInit(loop *Loop, handle *Signal) c.Int + +//go:linkname SignalStart C.uv_signal_start +func SignalStart(handle *Signal, cb SignalCb, signum c.Int) c.Int + +//go:linkname SignalStartOneshot C.uv_signal_start_oneshot +func SignalStartOneshot(handle *Signal, cb SignalCb, signum c.Int) c.Int diff --git a/c/libuv/timer.go b/c/libuv/timer.go new file mode 100644 index 00000000..0c31cb95 --- /dev/null +++ b/c/libuv/timer.go @@ -0,0 +1,56 @@ +package libuv + +import ( + _ "unsafe" + + "github.com/goplus/llgo/c" +) + +// ---------------------------------------------- + +/* Handle types. */ + +// TODO(spongehah): Timer +type Timer struct { + Unused [0]byte +} + +// ---------------------------------------------- + +// llgo:type Cgit +type TimerCb func(timer *Timer) + +// ---------------------------------------------- + +/* Timer related function and method */ + +//go:linkname InitTimer C.uv_timer_init +func InitTimer(loop *Loop, timer *Timer) c.Int + +// llgo:link (*Timer).Start C.uv_timer_start +func (timer *Timer) Start(cb TimerCb, timeout uint64, repeat uint64) c.Int { + return 0 +} + +// llgo:link (*Timer).Stop C.uv_timer_stop +func (timer *Timer) Stop() c.Int { + return 0 +} + +// llgo:link (*Timer).Again C.uv_timer_again +func (timer *Timer) Again() c.Int { + return 0 +} + +// llgo:link (*Timer).SetRepeat C.uv_timer_set_repeat +func (timer *Timer) SetRepeat(repeat uint64) {} + +// llgo:link (*Timer).GetRepeat C.uv_timer_get_repeat +func (timer *Timer) GetRepeat() uint64 { + return 0 +} + +// llgo:link (*Timer).GetDueIn C.uv_timer_get_due_in +func (timer *Timer) GetDueIn() uint64 { + return 0 +} diff --git a/c/lua/_demo/coroutine-cfunc/coroutine.go b/c/lua/_demo/coroutine-cfunc/coroutine.go new file mode 100644 index 00000000..676a5bfc --- /dev/null +++ b/c/lua/_demo/coroutine-cfunc/coroutine.go @@ -0,0 +1,42 @@ +package main + +import ( + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/lua" +) + +func coroutineFunc(L *lua.State) c.Int { + c.Printf(c.Str("Coroutine started\n")) + L.Yield(0) // Pause the coroutine + c.Printf(c.Str("Coroutine resumed\n")) + return 0 +} + +func main() { + L := lua.Newstate() + defer L.Close() + + L.Openlibs() + co := L.Newthread() + L.Pushcfunction(coroutineFunc) + L.Xmove(co, 1) + + var nres c.Int + + c.Printf(c.Str("Resuming coroutine for the first time\n")) + result := co.Resume(nil, 0, &nres) + if result == lua.YIELD { + c.Printf(c.Str("Coroutine yielded\n")) + result = co.Resume(nil, 0, &nres) + if result == 0 { + c.Printf(c.Str("Coroutine finished\n")) + } + } +} + +/* Expected output: +Resuming coroutine for the first time +Coroutine started +Coroutine yielded +Coroutine finished +*/ diff --git a/c/lua/_demo/coroutine/coroutine.go b/c/lua/_demo/coroutine/coroutine.go index feee0f66..ed05d98b 100644 --- a/c/lua/_demo/coroutine/coroutine.go +++ b/c/lua/_demo/coroutine/coroutine.go @@ -36,8 +36,8 @@ func main() { // Resume coroutine and handle yields for { status = co.Resume(nil, 0, &nres) - c.Printf(c.Str("Resuming coroutine %d...\n"), status) if status == lua.YIELD { + c.Printf(c.Str("Resuming coroutine %d...\n"), status) yieldValue := co.Tointeger(-1) c.Printf(c.Str("Yield value: %d\n"), yieldValue) co.Pop(1) // Clean up the stack @@ -58,3 +58,23 @@ func main() { finalStatus := co.Status() c.Printf(c.Str("Final status of coroutine: %d\n"), finalStatus) } + +/* Expected output: +Resuming coroutine... +Resuming coroutine 1... +Yield value: 1 +Coroutine is yieldable. +Resuming coroutine 1... +Yield value: 2 +Coroutine is yieldable. +Resuming coroutine 1... +Yield value: 3 +Coroutine is yieldable. +Resuming coroutine 1... +Yield value: 4 +Coroutine is yieldable. +Resuming coroutine 1... +Yield value: 5 +Coroutine is yieldable. +Final status of coroutine: 0 +*/ diff --git a/c/lua/_demo/metatable/metatable.go b/c/lua/_demo/metatable/metatable.go new file mode 100644 index 00000000..424fd5ed --- /dev/null +++ b/c/lua/_demo/metatable/metatable.go @@ -0,0 +1,125 @@ +package main + +import ( + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/lua" +) + +func toString(L *lua.State) c.Int { + L.Pushstring(c.Str("Hello from metatable!")) + return 1 +} + +func printStack(L *lua.State, message string) { + top := L.Gettop() + c.Printf(c.Str("%s - Stack size: %d\n"), c.AllocaCStr(message), c.Int(top)) + for i := c.Int(1); i <= top; i++ { + t := L.Type(i) + switch t { + case c.Int(lua.STRING): + c.Printf(c.Str(" %d: string: %s\n"), c.Int(i), L.Tostring(i)) + case c.Int(lua.BOOLEAN): + c.Printf(c.Str(" %d: boolean: %v\n"), c.Int(i), L.Toboolean(i)) + case c.Int(lua.NUMBER): + c.Printf(c.Str(" %d: number: %f\n"), c.Int(i), L.Tonumber(i)) + default: + c.Printf(c.Str(" %d: %s\n"), c.Int(i), L.Typename(t)) + } + } +} + +func main() { + L := lua.Newstate() + defer L.Close() + + L.Openlibs() + + L.Newtable() + printStack(L, "After creating main table") + + L.Newtable() + printStack(L, "After creating metatable") + + L.Pushcfunction(toString) + printStack(L, "After Push CFunction") + + L.Setfield(-2, c.Str("__tostring")) + printStack(L, "After setting __tostring") + + if L.Setmetatable(-2) == 0 { + c.Printf(c.Str("Failed to set metatable\n")) + } + printStack(L, "After setting metatable") + + L.Setglobal(c.Str("obj")) + printStack(L, "After setting global obj") + + testcode := c.Str(` + if obj == nil then + print('obj is not defined') + else + local mt = getmetatable(obj) + if not mt then + print('Metatable not set') + elseif not mt.__tostring then + print('__tostring not set in metatable') + else + print(mt.__tostring(obj)) + end + end + `) + + if L.Dostring(testcode) != lua.OK { + c.Printf(c.Str("Error: %s\n"), L.Tostring(-1)) + } + + L.Getglobal(c.Str("obj")) + if L.Getmetatable(-1) != 0 { + c.Printf(c.Str("Metatable get success\n")) + L.Pushstring(c.Str("__tostring")) + L.Gettable(-2) + if L.Isfunction(-1) { + c.Printf(c.Str("__tostring function found in metatable\n")) + if L.Iscfunction(-1) != 0 { + c.Printf(c.Str("__tostring is a C function\n")) + cfunc := L.Tocfunction(-1) + if cfunc != nil { + c.Printf(c.Str("Successfully retrieved __tostring C function pointer\n")) + L.Pushcfunction(cfunc) + if L.Call(0, 1) == lua.OK { + result := L.Tostring(-1) + c.Printf(c.Str("Result of calling __tostring: %s\n"), result) + } + } + } + } else { + c.Printf(c.Str("__tostring function not found in metatable\n")) + } + } else { + c.Printf(c.Str("No metatable found using GetTable\n")) + } +} + +/* Expected output: +After creating main table - Stack size: 1 + 1: table +After creating metatable - Stack size: 2 + 1: table + 2: table +After Push CFunction - Stack size: 3 + 1: table + 2: table + 3: function +After setting __tostring - Stack size: 2 + 1: table + 2: table +After setting metatable - Stack size: 1 + 1: table +After setting global obj - Stack size: 0 +Hello from metatable! +Metatable get success +__tostring function found in metatable +__tostring is a C function +Successfully retrieved __tostring C function pointer +Result of calling __tostring: Hello from metatable! +*/ diff --git a/c/lua/lua.go b/c/lua/lua.go index 1812f74a..86f3a532 100644 --- a/c/lua/lua.go +++ b/c/lua/lua.go @@ -41,17 +41,17 @@ type State struct { // ** basic types // */ const ( - NONE = int(-1) - NIL = int(0) - BOOLEAN = int(1) - LIGHTUSERDATA = int(2) - NUMBER = int(3) - STRING = int(4) - TABLE = int(5) - FUNCTION = int(6) - USERDATA = int(7) - THREAD = int(8) - UMTYPES = int(9) + NONE c.Int = -1 + NIL c.Int = 0 + BOOLEAN c.Int = 1 + LIGHTUSERDATA c.Int = 2 + NUMBER c.Int = 3 + STRING c.Int = 4 + TABLE c.Int = 5 + FUNCTION c.Int = 6 + USERDATA c.Int = 7 + THREAD c.Int = 8 + UMTYPES c.Int = 9 ) // /* minimum Lua stack available to a C function */ @@ -78,13 +78,15 @@ type Integer = c.Int type Unsigned = c.Uint // /* type for continuation-function contexts */ -// TODO(zzy): Context may not be c.Int -type KContext c.Int +type KContext = c.Pointer // /* // ** Type for C functions registered with Lua // */ +// llgo:type C +type CFunction func(L *State) c.Int + // /* // ** Type for continuation functions // */ @@ -144,7 +146,6 @@ type KFunction func(L *State, status c.Int, ctx KContext) c.Int func (L *State) Close() {} // State *(lua_newstate) (lua_Alloc f, void *ud); -// State *(lua_newthread) (State *L); // llgo:link (*State).Newthread C.lua_newthread func (L *State) Newthread() *State { return nil } @@ -185,9 +186,6 @@ func (L *State) Xmove(to *State, n c.Int) {} // /* // ** access functions (stack -> C) // */ -// LUA_API int (lua_isinteger) (State *L, int idx); -// llgo:link (*State).Isinteger C.lua_isinteger -func (L *State) Isinteger(idx c.Int) c.Int { return 0 } // llgo:link (*State).Isnumber C.lua_isnumber func (L *State) Isnumber(idx c.Int) c.Int { return 0 } @@ -195,11 +193,17 @@ func (L *State) Isnumber(idx c.Int) c.Int { return 0 } // llgo:link (*State).Isstring C.lua_isstring func (L *State) Isstring(idx c.Int) c.Int { return 0 } -// TODO(zzy):add to demo +// llgo:link (*State).Iscfunction C.lua_iscfunction +func (L *State) Iscfunction(idx c.Int) c.Int { return 0 } + +// llgo:link (*State).Isinteger C.lua_isinteger +func (L *State) Isinteger(idx c.Int) c.Int { return 0 } + +// LUA_API int (lua_isuserdata) (State *L, int idx); + // llgo:link (*State).Type C.lua_type func (L *State) Type(idx c.Int) c.Int { return 0 } -// TODO(zzy) // llgo:link (*State).Typename C.lua_typename func (L *State) Typename(tp c.Int) *c.Char { return nil } @@ -215,11 +219,11 @@ func (L *State) Toboolean(idx c.Int) bool { return false } // llgo:link (*State).Tolstring C.lua_tolstring func (L *State) Tolstring(idx c.Int, len *c.Ulong) *c.Char { return nil } -// LUA_API int (lua_iscfunction) (State *L, int idx); -// LUA_API int (lua_isuserdata) (State *L, int idx); - // LUA_API lua_Unsigned (lua_rawlen) (State *L, int idx); -// LUA_API lua_CFunction (lua_tocfunction) (State *L, int idx); + +// llgo:link (*State).Tocfunction C.lua_tocfunction +func (L *State) Tocfunction(idx c.Int) CFunction { return nil } + // LUA_API void *(lua_touserdata) (State *L, int idx); // LUA_API State *(lua_tothread) (State *L, int idx); // LUA_API const void *(lua_topointer) (State *L, int idx); @@ -240,24 +244,21 @@ func (L *State) Pushnumber(n Number) {} // llgo:link (*State).Pushinteger C.lua_pushinteger func (L *State) Pushinteger(n Integer) {} -// llgo:link (*State).Pushstring C.lua_pushstring -func (L *State) Pushstring(s *c.Char) *c.Char { - return nil -} - // llgo:link (*State).Pushlstring C.lua_pushlstring -func (L *State) Pushlstring(s *c.Char, len c.Ulong) *c.Char { - return nil -} +func (L *State) Pushlstring(s *c.Char, len c.Ulong) *c.Char { return nil } + +// llgo:link (*State).Pushstring C.lua_pushstring +func (L *State) Pushstring(s *c.Char) *c.Char { return nil } // llgo:link (*State).Pushfstring C.lua_pushfstring func (L *State) Pushfstring(format *c.Char, __llgo_va_list ...any) *c.Char { return nil } +// llgo:link (*State).Pushcclosure C.lua_pushcclosure +func (L *State) Pushcclosure(fn CFunction, n c.Int) {} + // llgo:link (*State).Pushboolean C.lua_pushboolean func (L *State) Pushboolean(b c.Int) {} -//const char *(lua_pushvfstring) (State *L, const char *fmt,va_list argp); -//void (lua_pushcclosure) (State *L, lua_CFunction fn, int n); //void (lua_pushlightuserdata) (State *L, void *p); //int (lua_pushthread) (State *L); @@ -274,23 +275,25 @@ func (L *State) Gettable(idx c.Int) c.Int { return 0 } // llgo:link (*State).Getfield C.lua_getfield func (L *State) Getfield(idx c.Int, k *c.Char) c.Int { return 0 } -// llgo:link (*State).Createtable C.lua_createtable -func (L *State) Createtable(narr c.Int, nrec c.Int) {} - // LUA_API int (lua_geti) (State *L, int idx, lua_Integer n); // LUA_API int (lua_rawget) (State *L, int idx); // LUA_API int (lua_rawgeti) (State *L, int idx, lua_Integer n); // LUA_API int (lua_rawgetp) (State *L, int idx, const void *p); +// llgo:link (*State).Createtable C.lua_createtable +func (L *State) Createtable(narr c.Int, nrec c.Int) {} + // LUA_API void *(lua_newuserdatauv) (State *L, size_t sz, int nuvalue); -// LUA_API int (lua_getmetatable) (State *L, int objindex); + +// llgo:link (*State).Getmetatable C.lua_getmetatable +func (L *State) Getmetatable(objindex c.Int) c.Int { return 0 } + // LUA_API int (lua_getiuservalue) (State *L, int idx, int n); // /* // ** set functions (stack -> Lua) // */ -// TODO(zzy):add to demo // llgo:link (*State).Setglobal C.lua_setglobal func (L *State) Setglobal(name *c.Char) {} @@ -304,25 +307,34 @@ func (L *State) Setfield(idx c.Int, k *c.Char) {} //void (lua_rawset) (State *L, int idx); //void (lua_rawseti) (State *L, int idx, lua_Integer n); //void (lua_rawsetp) (State *L, int idx, const void *p); -//int (lua_setmetatable) (State *L, int objindex); + +// llgo:link (*State).Setmetatable C.lua_setmetatable +func (L *State) Setmetatable(objindex c.Int) c.Int { return 0 } + //int (lua_setiuservalue) (State *L, int idx, int n); // /* // ** 'load' and 'call' functions (load and run Lua code) // */ +// llgo:link (*State).Callk C.lua_callk +func (L *State) Callk(nargs c.Int, nresults c.Int, ctx KContext, k KFunction) c.Int { + return 0 +} + +func (L *State) Call(nargs c.Int, nresults c.Int) c.Int { + return L.Callk(nargs, nresults, nil, nil) +} + // llgo:link (*State).Pcallk C.lua_pcallk -func (L *State) Pcallk(nargs c.Int, nresults c.Int, errfunc c.Int, ctx KContext, k *KFunction) c.Int { +func (L *State) Pcallk(nargs c.Int, nresults c.Int, errfunc c.Int, ctx KContext, k KFunction) c.Int { return 0 } func (L *State) Pcall(nargs c.Int, nresults c.Int, errfunc c.Int) c.Int { - return L.Pcallk(nargs, nresults, errfunc, KContext(c.Int(0)), nil) + return L.Pcallk(nargs, nresults, errfunc, nil, nil) } -// void (lua_callk) (State *L, int nargs, int nresults, lua_KContext ctx, lua_KFunction k); -// #define lua_call(L,n,r) lua_callk(L, (n), (r), 0, NULL) - // int (lua_load) (State *L, lua_Reader reader, void *dt, const char *chunkname, const char *mode); // int (lua_dump) (State *L, lua_Writer writer, void *data, int strip); @@ -340,9 +352,9 @@ func (L *State) Status() c.Int { return 0 } // llgo:link (*State).Isyieldable C.lua_isyieldable func (L *State) Isyieldable() c.Int { return 0 } -// TODO(zzy) -// int (lua_yieldk) (State *L, int nresults, lua_KContext ctx, lua_KFunction k); -// #define lua_yield(L,n) lua_yieldk(L, (n), 0, NULL) +// llgo:link (*State).Yieldk C.lua_yieldk +func (L *State) Yieldk(nresults c.Int, ctx KContext, k KFunction) c.Int { return 0 } +func (L *State) Yield(nresults c.Int) c.Int { return L.Yieldk(nresults, nil, nil) } // /* // ** Warning-related functions @@ -396,11 +408,17 @@ func (L *State) Next(idx c.Int) c.Int { return 0 } // ** =============================================================== // */ -func (L *State) Tonumber(idx c.Int) Number { return L.Tonumberx(idx, nil) } -func (L *State) Tostring(idx c.Int) *c.Char { return L.Tolstring(idx, nil) } -func (L *State) Tointeger(idx c.Int) Integer { return L.Tointegerx(idx, nil) } -func (L *State) Pop(n c.Int) { L.Settop(-(n) - 1) } -func (L *State) Newtable() { L.Createtable(0, 0) } +// #define lua_getextraspace(L) ((void *)((char *)(L) - LUA_EXTRASPACE)) + +func (L *State) Tonumber(idx c.Int) Number { return L.Tonumberx(idx, nil) } +func (L *State) Tostring(idx c.Int) *c.Char { return L.Tolstring(idx, nil) } +func (L *State) Tointeger(idx c.Int) Integer { return L.Tointegerx(idx, nil) } +func (L *State) Pop(n c.Int) { L.Settop(-(n) - 1) } +func (L *State) Newtable() { L.Createtable(0, 0) } + +// #define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n))) +func (L *State) Pushcfunction(f CFunction) { L.Pushcclosure(f, 0) } + func (L *State) Isfunction(n c.Int) bool { return L.Type(n) == c.Int(FUNCTION) } func (L *State) Istable(n c.Int) bool { return L.Type(n) == c.Int(TABLE) } func (L *State) Islightuserdata(n c.Int) bool { return L.Type(n) == c.Int(LIGHTUSERDATA) } @@ -410,20 +428,11 @@ func (L *State) Isthread(n c.Int) bool { return L.Type(n) == c.Int(THREAD func (L *State) Isnone(n c.Int) bool { return L.Type(n) == c.Int(NONE) } func (L *State) Isnoneornil(n c.Int) bool { return L.Type(n) <= 0 } -// #define lua_getextraspace(L) ((void *)((char *)(L) - LUA_EXTRASPACE)) - -// #define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n))) - -// #define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0) - // #define lua_pushliteral(L, s) lua_pushstring(L, "" s) - // #define lua_pushglobaltable(L) ((void)lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)) // #define lua_insert(L,idx) lua_rotate(L, (idx), 1) - // #define lua_remove(L,idx) (lua_rotate(L, (idx), -1), lua_pop(L, 1)) - // #define lua_replace(L,idx) (lua_copy(L, -1, (idx)), lua_pop(L, 1)) // /* }============================================================== */ diff --git a/c/net/net.go b/c/net/net.go index 7b2608aa..e811d316 100644 --- a/c/net/net.go +++ b/c/net/net.go @@ -80,6 +80,23 @@ const ( SOCK_SEQPACKET = 5 // sequenced packet stream ) +const ( + EAI_ADDRFAMILY = iota + 1 /* address family for hostname not supported */ + EAI_AGAIN /* temporary failure in name resolution */ + EAI_BADFLAGS /* invalid value for ai_flags */ + EAI_FAIL /* non-recoverable failure in name resolution */ + EAI_FAMILY /* ai_family not supported */ + EAI_MEMORY /* memory allocation failure */ + EAI_NODATA /* no address associated with hostname */ + EAI_NONAME /* hostname nor servname provided, or not known */ + EAI_SERVICE /* servname not supported for ai_socktype */ + EAI_SOCKTYPE /* ai_socktype not supported */ + EAI_SYSTEM /* system error returned in errno */ + EAI_BADHINTS /* invalid value for hints */ + EAI_PROTOCOL /* resolved protocol is unknown */ + EAI_OVERFLOW /* argument buffer overflow */ +) + // (TODO) merge to inet const INET_ADDRSTRLEN = 16 @@ -91,10 +108,23 @@ type SockaddrIn struct { Zero [8]c.Char } +type SockaddrIn6 struct { + Len uint8 + Family uint8 + Port uint16 + Flowinfo c.Uint + Addr In6Addr + ScopeId c.Uint +} + type InAddr struct { Addr c.Uint } +type In6Addr struct { + U6Addr [16]uint8 +} + type SockAddr struct { Len uint8 Family uint8 diff --git a/c/openssl/_demo/cbigintdemo/fib.go b/c/openssl/_demo/cbigintdemo/fib.go new file mode 100644 index 00000000..0d7648a9 --- /dev/null +++ b/c/openssl/_demo/cbigintdemo/fib.go @@ -0,0 +1,44 @@ +package main + +import ( + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/openssl" +) + +func newInt(n openssl.BN_ULONG) *openssl.BIGNUM { + ret := openssl.BNNew() + ret.SetWord(n) + return ret +} + +func main() { + ctx := openssl.BN_CTXNew() + defer ctx.Free() + + // Initialize two big ints with the first two numbers in the sequence. + a := newInt(0) + b := newInt(1) + defer a.Free() + defer b.Free() + + // Initialize limit as 10^99, the smallest integer with 100 digits. + v10, v99 := newInt(10), newInt(99) + defer v10.Free() + defer v99.Free() + + limit := openssl.BNNew() + defer limit.Free() + + limit.Exp(v10, v99, ctx) + + // Loop while a is smaller than 1e100. + for a.Cmp(limit) < 0 { + // Compute the next Fibonacci number, storing it in a. + a.Add(a, b) + // Swap a and b so that b is the next number in the sequence. + a, b = b, a + } + cstr := a.CStr() + c.Printf(c.Str("%s\n"), cstr) // 100-digit Fibonacci number + openssl.FreeCStr(cstr) +} diff --git a/c/openssl/_demo/cmd5demo/md5.go b/c/openssl/_demo/cmd5demo/md5.go new file mode 100644 index 00000000..26db4527 --- /dev/null +++ b/c/openssl/_demo/cmd5demo/md5.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + "unsafe" + + "github.com/goplus/llgo/c/openssl" +) + +func main() { + var md5 openssl.MD5_CTX + var h = make([]byte, openssl.MD5_LBLOCK) + md5.Init() + md5.UpdateString("The fog is getting thicker!") + md5.UpdateString("And Leon's getting laaarger!") + md5.Final(unsafe.SliceData(h)) + fmt.Printf("%x", h) +} diff --git a/c/openssl/_demo/cranddemo/rand.go b/c/openssl/_demo/cranddemo/rand.go new file mode 100644 index 00000000..4fc3a803 --- /dev/null +++ b/c/openssl/_demo/cranddemo/rand.go @@ -0,0 +1,17 @@ +package main + +import ( + "fmt" + + "github.com/goplus/llgo/c/openssl" +) + +func main() { + b := make([]byte, 10) + + openssl.RANDBytes(b) + fmt.Printf("%x\n", b) + + openssl.RANDPrivBytes(b) + fmt.Printf("%x\n", b) +} diff --git a/c/openssl/_demo/cshademo/sha.go b/c/openssl/_demo/cshademo/sha.go new file mode 100644 index 00000000..ee2577cb --- /dev/null +++ b/c/openssl/_demo/cshademo/sha.go @@ -0,0 +1,68 @@ +package main + +import ( + "fmt" + "unsafe" + + "github.com/goplus/llgo/c/openssl" +) + +func main() { + str := "His money is twice tainted:" + + var sha1 openssl.SHA_CTX + sha1.Init() + sha1.UpdateString(str) + + h1 := make([]byte, openssl.SHA_DIGEST_LENGTH) + sha1.Final(unsafe.SliceData(h1)) + fmt.Printf("%x\n", h1) + + h2 := make([]byte, openssl.SHA_DIGEST_LENGTH) + openssl.SHA1String(str, unsafe.SliceData(h2)) + fmt.Printf("%x\n", h2) + + var sha256 openssl.SHA256_CTX + sha256.Init() + sha256.UpdateString(str) + h3 := make([]byte, openssl.SHA256_DIGEST_LENGTH) + sha256.Final(unsafe.SliceData(h3)) + fmt.Printf("%x\n", h3) + + h4 := make([]byte, openssl.SHA256_DIGEST_LENGTH) + openssl.SHA256String(str, unsafe.SliceData(h4)) + fmt.Printf("%x\n", h4) + + var sha512 openssl.SHA512_CTX + sha512.Init() + sha512.UpdateString("His money is twice tainted:") + + h5 := make([]byte, openssl.SHA512_DIGEST_LENGTH) + sha512.Final(unsafe.SliceData(h5)) + fmt.Printf("%x\n", h5) + + h6 := make([]byte, openssl.SHA512_DIGEST_LENGTH) + openssl.SHA512String(str, unsafe.SliceData(h6)) + fmt.Printf("%x\n", h6) + + var sha224 openssl.SHA224_CTX + sha224.Init() + sha224.UpdateString(str) + + h7 := make([]byte, openssl.SHA224_DIGEST_LENGTH) + sha224.Final(unsafe.SliceData(h7)) + fmt.Printf("%x\n", h7) + h8 := make([]byte, openssl.SHA224_DIGEST_LENGTH) + openssl.SHA224String(str, unsafe.SliceData(h8)) + fmt.Printf("%x\n", h8) + + var sha384 openssl.SHA384_CTX + sha384.Init() + sha384.UpdateString(str) + h9 := make([]byte, openssl.SHA384_DIGEST_LENGTH) + sha384.Final(unsafe.SliceData(h9)) + fmt.Printf("%x\n", h9) + h10 := make([]byte, openssl.SHA384_DIGEST_LENGTH) + openssl.SHA384String(str, unsafe.SliceData(h10)) + fmt.Printf("%x\n", h10) +} diff --git a/c/openssl/_wrap/openssl.c b/c/openssl/_wrap/openssl.c new file mode 100644 index 00000000..7cc5bbdb --- /dev/null +++ b/c/openssl/_wrap/openssl.c @@ -0,0 +1,5 @@ +#include + +void opensslFree(void *ptr) { + OPENSSL_free(ptr); +} diff --git a/c/openssl/bio.go b/c/openssl/bio.go new file mode 100644 index 00000000..f03fcb83 --- /dev/null +++ b/c/openssl/bio.go @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 openssl + +import ( + "unsafe" + + "github.com/goplus/llgo/c" +) + +// ----------------------------------------------------------------------------- + +type BIO struct { + Unused [0]byte +} + +// BIO *BIO_new_mem_buf(const void *buf, int len); +// +//go:linkname BIONewMemBuf C.BIO_new_mem_buf +func BIONewMemBuf(buf unsafe.Pointer, len c.Int) *BIO + +// int BIO_free(BIO *a); +// +// llgo:link (*BIO).Free C.BIO_free +func (*BIO) Free() c.Int { return 0 } + +// int BIO_up_ref(BIO *a); +// +// llgo:link (*BIO).UpRef C.BIO_up_ref +func (*BIO) UpRef() c.Int { return 0 } + +// int BIO_read_ex(BIO *b, void *data, size_t dlen, size_t *readbytes); +// +// llgo:link (*BIO).ReadEx C.BIO_read_ex +func (*BIO) ReadEx(data unsafe.Pointer, dlen uintptr, readbytes *uintptr) c.Int { return 0 } + +// int BIO_write(BIO *b, const void *data, int dlen); +// +// llgo:link (*BIO).Write C.BIO_write +func (*BIO) Write(data unsafe.Pointer, dlen c.Int) c.Int { return 0 } + +// int BIO_write_ex(BIO *b, const void *data, size_t dlen, size_t *written); +// +// llgo:link (*BIO).WriteEx C.BIO_write_ex +func (*BIO) WriteEx(data unsafe.Pointer, dlen uintptr, written *uintptr) c.Int { return 0 } + +// ----------------------------------------------------------------------------- diff --git a/c/openssl/bn.go b/c/openssl/bn.go new file mode 100644 index 00000000..4a765b1c --- /dev/null +++ b/c/openssl/bn.go @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 openssl + +import ( + _ "unsafe" + + "github.com/goplus/llgo/c" +) + +type BN_ULONG = uint64 + +// ----------------------------------------------------------------------------- + +type BN_CTX struct { + Unused [0]byte +} + +// BN_CTX *BN_CTX_new(void); +// +//go:linkname BN_CTXNew C.BN_CTX_new +func BN_CTXNew() *BN_CTX + +// BN_CTX *BN_CTX_secure_new(void); +// +//go:linkname BN_CTXSecureNew C.BN_CTX_secure_new +func BN_CTXSecureNew() *BN_CTX + +// BN_CTX *BN_CTX_new_ex(OSSL_LIB_CTX *ctx); +// BN_CTX *BN_CTX_secure_new_ex(OSSL_LIB_CTX *ctx); + +// void BN_CTX_free(BN_CTX *c); +// +// llgo:link (*BN_CTX).Free C.BN_CTX_free +func (*BN_CTX) Free() {} + +// void BN_CTX_start(BN_CTX *ctx); +// BIGNUM *BN_CTX_get(BN_CTX *ctx); +// void BN_CTX_end(BN_CTX *ctx); + +// ----------------------------------------------------------------------------- + +type BIGNUM struct { + Unused [0]byte +} + +// BIGNUM *BN_new(void); +// +//go:linkname BNNew C.BN_new +func BNNew() *BIGNUM + +// BIGNUM *BN_secure_new(void); +// +//go:linkname BNSecureNew C.BN_secure_new +func BNSecureNew() *BIGNUM + +// void BN_free(BIGNUM *a); +// +// llgo:link (*BIGNUM).Free C.BN_free +func (*BIGNUM) Free() {} + +// void BN_clear_free(BIGNUM *a); +// +// llgo:link (*BIGNUM).ClearFree C.BN_clear_free +func (*BIGNUM) ClearFree() {} + +// void BN_clear(BIGNUM *a); +// +// llgo:link (*BIGNUM).Clear C.BN_clear +func (*BIGNUM) Clear() {} + +// BIGNUM *BN_dup(const BIGNUM *a); +// +// llgo:link (*BIGNUM).Dup C.BN_dup +func (*BIGNUM) Dup() *BIGNUM { return nil } + +// BIGNUM *BN_copy(BIGNUM *a, const BIGNUM *b); +// +// llgo:link (*BIGNUM).Copy C.BN_copy +func (*BIGNUM) Copy(b *BIGNUM) *BIGNUM { return nil } + +// void BN_swap(BIGNUM *a, BIGNUM *b); +// +// llgo:link (*BIGNUM).Swap C.BN_swap +func (*BIGNUM) Swap(b *BIGNUM) {} + +// int BN_is_zero(const BIGNUM *a); +// +// llgo:link (*BIGNUM).IsZero C.BN_is_zero +func (*BIGNUM) IsZero() c.Int { return 0 } + +// void BN_zero_ex(BIGNUM *a); +// +// llgo:link (*BIGNUM).SetZero C.BN_zero_ex +func (*BIGNUM) SetZero() {} + +// int BN_is_one(const BIGNUM *a); +// +// llgo:link (*BIGNUM).IsOne C.BN_is_one +func (*BIGNUM) IsOne() c.Int { return 0 } + +// int BN_is_odd(const BIGNUM *a); +// +// llgo:link (*BIGNUM).IsOdd C.BN_is_odd +func (*BIGNUM) IsOdd() c.Int { return 0 } + +// int BN_abs_is_word(const BIGNUM *a, const BN_ULONG w); +// +// llgo:link (*BIGNUM).AbsIsWord C.BN_abs_is_word +func (*BIGNUM) AbsIsWord(w BN_ULONG) c.Int { return 0 } + +// int BN_is_word(const BIGNUM *a, const BN_ULONG w); +// +// llgo:link (*BIGNUM).IsWord C.BN_is_word +func (*BIGNUM) IsWord(w BN_ULONG) c.Int { return 0 } + +// int BN_set_word(BIGNUM *a, BN_ULONG w); +// +// llgo:link (*BIGNUM).SetWord C.BN_set_word +func (*BIGNUM) SetWord(w BN_ULONG) c.Int { return 0 } + +// BN_ULONG BN_get_word(const BIGNUM *a); +// +// llgo:link (*BIGNUM).GetWord C.BN_get_word +func (*BIGNUM) GetWord() BN_ULONG { return 0 } + +// BN_ULONG BN_mod_word(const BIGNUM *a, BN_ULONG w); +// +// llgo:link (*BIGNUM).ModWord C.BN_mod_word +func (*BIGNUM) ModWord(w BN_ULONG) BN_ULONG { return 0 } + +// BN_ULONG BN_div_word(BIGNUM *a, BN_ULONG w); +// +// llgo:link (*BIGNUM).DivWord C.BN_div_word +func (*BIGNUM) DivWord(w BN_ULONG) BN_ULONG { return 0 } + +// int BN_mul_word(BIGNUM *a, BN_ULONG w); +// +// llgo:link (*BIGNUM).MulWord C.BN_mul_word +func (*BIGNUM) MulWord(w BN_ULONG) c.Int { return 0 } + +// int BN_add_word(BIGNUM *a, BN_ULONG w); +// +// llgo:link (*BIGNUM).AddWord C.BN_add_word +func (*BIGNUM) AddWord(w BN_ULONG) c.Int { return 0 } + +// int BN_sub_word(BIGNUM *a, BN_ULONG w); +// +// llgo:link (*BIGNUM).SubWord C.BN_sub_word +func (*BIGNUM) SubWord(w BN_ULONG) c.Int { return 0 } + +// char *BN_bn2hex(const BIGNUM *a); +// +// llgo:link (*BIGNUM).Bn2hex C.BN_bn2hex +func (*BIGNUM) Bn2hex() *c.Char { return nil } + +// char *BN_bn2dec(const BIGNUM *a); +// +// llgo:link (*BIGNUM).Bn2dec C.BN_bn2dec +func (*BIGNUM) Bn2dec() *c.Char { return nil } + +// llgo:link (*BIGNUM).CStr C.BN_bn2dec +func (*BIGNUM) CStr() *c.Char { return nil } + +// int BN_hex2bn(BIGNUM **a, const char *str); +// +//go:linkname BNHex2bn C.BN_hex2bn +func BNHex2bn(a **BIGNUM, str *c.Char) c.Int + +// int BN_dec2bn(BIGNUM **a, const char *str); +// +//go:linkname BNDec2bn C.BN_dec2bn +func BNDec2bn(a **BIGNUM, str *c.Char) c.Int + +// int BN_asc2bn(BIGNUM **a, const char *str); +// +//go:linkname BNAsc2bn C.BN_asc2bn +func BNAsc2bn(a **BIGNUM, str *c.Char) c.Int + +/* +BIGNUM *BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret); +BIGNUM *BN_signed_bin2bn(const unsigned char *s, int len, BIGNUM *ret); +int BN_bn2bin(const BIGNUM *a, unsigned char *to); +int BN_bn2binpad(const BIGNUM *a, unsigned char *to, int tolen); +int BN_signed_bn2bin(const BIGNUM *a, unsigned char *to, int tolen); +BIGNUM *BN_lebin2bn(const unsigned char *s, int len, BIGNUM *ret); +BIGNUM *BN_signed_lebin2bn(const unsigned char *s, int len, BIGNUM *ret); +int BN_bn2lebinpad(const BIGNUM *a, unsigned char *to, int tolen); +int BN_signed_bn2lebin(const BIGNUM *a, unsigned char *to, int tolen); +BIGNUM *BN_native2bn(const unsigned char *s, int len, BIGNUM *ret); +BIGNUM *BN_signed_native2bn(const unsigned char *s, int len, BIGNUM *ret); +int BN_bn2nativepad(const BIGNUM *a, unsigned char *to, int tolen); +int BN_signed_bn2native(const BIGNUM *a, unsigned char *to, int tolen); +BIGNUM *BN_mpi2bn(const unsigned char *s, int len, BIGNUM *ret); +int BN_bn2mpi(const BIGNUM *a, unsigned char *to); +*/ + +// int BN_sub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b); +// +// llgo:link (*BIGNUM).Sub C.BN_sub +func (*BIGNUM) Sub(a, b *BIGNUM) c.Int { return 0 } + +// int BN_add(BIGNUM *r, const BIGNUM *a, const BIGNUM *b); +// +// llgo:link (*BIGNUM).Add C.BN_add +func (*BIGNUM) Add(a, b *BIGNUM) c.Int { return 0 } + +// int BN_usub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b); +// +// llgo:link (*BIGNUM).Usub C.BN_usub +func (*BIGNUM) Usub(a, b *BIGNUM) c.Int { return 0 } + +// int BN_uadd(BIGNUM *r, const BIGNUM *a, const BIGNUM *b); +// +// llgo:link (*BIGNUM).Uadd C.BN_uadd +func (*BIGNUM) Uadd(a, b *BIGNUM) c.Int { return 0 } + +// int BN_mul(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx); +// +// llgo:link (*BIGNUM).Mul C.BN_mul +func (*BIGNUM) Mul(r, a, b *BIGNUM, ctx *BN_CTX) c.Int { return 0 } + +// int BN_sqr(BIGNUM *r, const BIGNUM *a, BN_CTX *ctx); +// +// llgo:link (*BIGNUM).Sqr C.BN_sqr +func (*BIGNUM) Sqr(r, a *BIGNUM, ctx *BN_CTX) c.Int { return 0 } + +/** BN_set_negative sets sign of a BIGNUM + * \param b pointer to the BIGNUM object + * \param n 0 if the BIGNUM b should be positive and a value != 0 otherwise + */ +// void BN_set_negative(BIGNUM *b, int n); +// +// llgo:link (*BIGNUM).SetNegative C.BN_set_negative +func (*BIGNUM) SetNegative(n c.Int) {} + +/** BN_is_negative returns 1 if the BIGNUM is negative + * \param b pointer to the BIGNUM object + * \return 1 if a < 0 and 0 otherwise + */ +// int BN_is_negative(const BIGNUM *b); +// +// llgo:link (*BIGNUM).IsNegative C.BN_is_negative +func (*BIGNUM) IsNegative() c.Int { return 0 } + +// int BN_div(BIGNUM *dv, BIGNUM *rem, const BIGNUM *m, const BIGNUM *d, BN_CTX *ctx); +// +// llgo:link (*BIGNUM).Div C.BN_div +func (*BIGNUM) Div(rem, m, d *BIGNUM, ctx *BN_CTX) c.Int { return 0 } + +// int BN_nnmod(BIGNUM *r, const BIGNUM *m, const BIGNUM *d, BN_CTX *ctx); +// +// llgo:link (*BIGNUM).Nnmod C.BN_nnmod +func (*BIGNUM) Nnmod(r, m, d *BIGNUM, ctx *BN_CTX) c.Int { return 0 } + +// int BN_cmp(const BIGNUM *a, const BIGNUM *b); +// +// llgo:link (*BIGNUM).Cmp C.BN_cmp +func (*BIGNUM) Cmp(b *BIGNUM) c.Int { return 0 } + +// int BN_ucmp(const BIGNUM *a, const BIGNUM *b); +// +// llgo:link (*BIGNUM).Ucmp C.BN_ucmp +func (*BIGNUM) Ucmp(b *BIGNUM) c.Int { return 0 } + +// int BN_is_bit_set(const BIGNUM *a, int n); +// +// llgo:link (*BIGNUM).IsBitSet C.BN_is_bit_set +func (*BIGNUM) IsBitSet(n c.Int) c.Int { return 0 } + +// int BN_set_bit(BIGNUM *a, int n); +// +// llgo:link (*BIGNUM).SetBit C.BN_set_bit +func (*BIGNUM) SetBit(n c.Int) c.Int { return 0 } + +// int BN_clear_bit(BIGNUM *a, int n); +// +// llgo:link (*BIGNUM).ClearBit C.BN_clear_bit +func (*BIGNUM) ClearBit(n c.Int) c.Int { return 0 } + +// int BN_lshift(BIGNUM *r, const BIGNUM *a, int n); +// +// llgo:link (*BIGNUM).Lshift C.BN_lshift +func (*BIGNUM) Lshift(a *BIGNUM, n c.Int) c.Int { return 0 } + +// int BN_lshift1(BIGNUM *r, const BIGNUM *a); +// +// llgo:link (*BIGNUM).Lshift1 C.BN_lshift1 +func (*BIGNUM) Lshift1(a *BIGNUM) c.Int { return 0 } + +// int BN_rshift(BIGNUM *r, const BIGNUM *a, int n); +// +// llgo:link (*BIGNUM).Rshift C.BN_rshift +func (*BIGNUM) Rshift(a *BIGNUM, n c.Int) c.Int { return 0 } + +// int BN_rshift1(BIGNUM *r, const BIGNUM *a); +// +// llgo:link (*BIGNUM).Rshift1 C.BN_rshift1 +func (*BIGNUM) Rshift1(a *BIGNUM) c.Int { return 0 } + +// int BN_exp(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, BN_CTX *ctx); +// +// llgo:link (*BIGNUM).Exp C.BN_exp +func (*BIGNUM) Exp(a, p *BIGNUM, ctx *BN_CTX) c.Int { return 0 } + +// int BN_mod_exp(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, const BIGNUM *m, BN_CTX *ctx); +// +// llgo:link (*BIGNUM).ModExp C.BN_mod_exp +func (*BIGNUM) ModExp(a, p, m *BIGNUM, ctx *BN_CTX) c.Int { return 0 } + +// int BN_gcd(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx); +// +// llgo:link (*BIGNUM).Gcd C.BN_gcd +func (*BIGNUM) Gcd(a, b *BIGNUM, ctx *BN_CTX) c.Int { return 0 } + +// int BN_are_coprime(const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx); +// +// llgo:link (*BIGNUM).AreCoprime C.BN_are_coprime +func (*BIGNUM) AreCoprime(b *BIGNUM, ctx *BN_CTX) c.Int { return 0 } + +// ----------------------------------------------------------------------------- + +type BN_GENCB struct { + Unused [0]byte +} + +// ----------------------------------------------------------------------------- diff --git a/c/openssl/err.go b/c/openssl/err.go new file mode 100644 index 00000000..02fb7969 --- /dev/null +++ b/c/openssl/err.go @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 openssl + +import ( + "unsafe" + + "github.com/goplus/llgo/c" +) + +// ----------------------------------------------------------------------------- + +/*- + * The error code packs differently depending on if it records a system + * error or an OpenSSL error. + * + * A system error packs like this (we follow POSIX and only allow positive + * numbers that fit in an |int|): + * + * +-+-------------------------------------------------------------+ + * |1| system error number | + * +-+-------------------------------------------------------------+ + * + * An OpenSSL error packs like this: + * + * <---------------------------- 32 bits --------------------------> + * <--- 8 bits ---><------------------ 23 bits -----------------> + * +-+---------------+---------------------------------------------+ + * |0| library | reason | + * +-+---------------+---------------------------------------------+ + * + * A few of the reason bits are reserved as flags with special meaning: + * + * <5 bits-<>--------- 19 bits -----------------> + * +-------+-+-----------------------------------+ + * | rflags| | reason | + * +-------+-+-----------------------------------+ + * ^ + * | + * ERR_RFLAG_FATAL = ERR_R_FATAL + * + * The reason flags are part of the overall reason code for practical + * reasons, as they provide an easy way to place different types of + * reason codes in different numeric ranges. + * + * The currently known reason flags are: + * + * ERR_RFLAG_FATAL Flags that the reason code is considered fatal. + * For backward compatibility reasons, this flag + * is also the code for ERR_R_FATAL (that reason + * code served the dual purpose of flag and reason + * code in one in pre-3.0 OpenSSL). + * ERR_RFLAG_COMMON Flags that the reason code is common to all + * libraries. All ERR_R_ macros must use this flag, + * and no other _R_ macro is allowed to use it. + */ +type Errno c.Ulong + +// ERR_get_error returns the earliest error code from the thread's error queue and +// removes the entry. This function can be called repeatedly until there are no more +// error codes to return. +// +// unsigned long ERR_get_error(void); +// +//go:linkname ERRGetError C.ERR_get_error +func ERRGetError() Errno + +// ERR_get_error_all() is the same as ERR_get_error(), but on success it additionally +// stores the filename, line number and function where the error occurred in *file, +// *line and *func, and also extra text and flags in *data, *flags. If any of those +// parameters are NULL, it will not be changed. +// +// unsigned long ERR_get_error_all( +// const char **file, int *line, const char **func, const char **data, int *flags); +// +//go:linkname ERRGetErrorAll C.ERR_get_error_all +func ERRGetErrorAll( + file **c.Char, line *c.Int, function **c.Char, data **c.Char, flags *c.Int) Errno + +// unsigned long ERR_peek_error(void); +// +//go:linkname ERRPeekError C.ERR_peek_error +func ERRPeekError() Errno + +// unsigned long ERR_peek_error_all( +// const char **file, int *line, const char **func, const char **data, int *flags); +// +//go:linkname ERRPeekErrorAll C.ERR_peek_error_all +func ERRPeekErrorAll( + file **c.Char, line *c.Int, function **c.Char, data **c.Char, flags *c.Int) Errno + +// unsigned long ERR_peek_last_error(void); +// +//go:linkname ERRPeekLastError C.ERR_peek_last_error +func ERRPeekLastError() Errno + +// unsigned long ERR_peek_last_error_all( +// const char **file, int *line, const char **func, const char **data, int *flags); +// +//go:linkname ERRPeekLastErrorAll C.ERR_peek_last_error_all +func ERRPeekLastErrorAll( + file **c.Char, line *c.Int, function **c.Char, data **c.Char, flags *c.Int) Errno + +// void ERR_clear_error(void); +// +//go:linkname ERRClearError C.ERR_clear_error +func ERRClearError() + +// ERR_error_string() generates a human-readable string representing the error code e, +// and places it at buf. buf must be at least 256 bytes long. +// +// char *ERR_error_string(unsigned long e, char *buf); +// +//go:linkname ERRErrorString C.ERR_error_string +func ERRErrorString(e Errno, buf *c.Char) *c.Char + +// ERR_lib_error_string() and ERR_reason_error_string() return the library name and +// reason string respectively. +// +// const char *ERR_lib_error_string(unsigned long e); +// +//go:linkname ERRLibErrorString C.ERR_lib_error_string +func ERRLibErrorString(e Errno) *c.Char + +// const char *ERR_reason_error_string(unsigned long e); +// +//go:linkname ERRReasonErrorString C.ERR_reason_error_string +func ERRReasonErrorString(e Errno) *c.Char + +// void ERR_print_errors_cb(int (*cb) (const char *str, size_t len, void *u), void *u); +// +// [pid]:error:[error code]:[library name]:[function name]:[reason string]:[filename]:[line]:[optional text message] +// +//go:linkname ERRPrintErrorsCb C.ERR_print_errors_cb +func ERRPrintErrorsCb(cb func(str *c.Char, len uintptr, u unsafe.Pointer) c.Int, u unsafe.Pointer) + +// void ERR_print_errors_fp(FILE *fp); +// +//go:linkname ERRPrintErrorsFp C.ERR_print_errors_fp +func ERRPrintErrorsFp(fp c.FilePtr) + +// ----------------------------------------------------------------------------- diff --git a/c/openssl/md5.go b/c/openssl/md5.go new file mode 100644 index 00000000..99aac0c3 --- /dev/null +++ b/c/openssl/md5.go @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 openssl + +import ( + "unsafe" + + "github.com/goplus/llgo/c" +) + +// ----------------------------------------------------------------------------- + +const ( + MD5_CBLOCK = 64 + MD5_LBLOCK = MD5_CBLOCK / 4 +) + +// ----------------------------------------------------------------------------- + +type MD5_LONG = c.Uint + +type MD5_CTX struct { + A, B, C, D MD5_LONG + Nl, Nh MD5_LONG + Data [MD5_LBLOCK]MD5_LONG + Num c.Uint +} + +// OSSL_DEPRECATEDIN_3_0 int MD5_Init(MD5_CTX *c); +// +// llgo:link (*MD5_CTX).Init C.MD5_Init +func (c *MD5_CTX) Init() c.Int { return 0 } + +// OSSL_DEPRECATEDIN_3_0 int MD5_Update(MD5_CTX *c, const void *data, size_t len); +// +// llgo:link (*MD5_CTX).Update C.MD5_Update +func (c *MD5_CTX) Update(data unsafe.Pointer, n uintptr) c.Int { return 0 } + +func (c *MD5_CTX) UpdateBytes(data []byte) c.Int { + return c.Update(unsafe.Pointer(unsafe.SliceData(data)), uintptr(len(data))) +} + +func (c *MD5_CTX) UpdateString(data string) c.Int { + return c.Update(unsafe.Pointer(unsafe.StringData(data)), uintptr(len(data))) +} + +// OSSL_DEPRECATEDIN_3_0 int MD5_Final(unsigned char *md, MD5_CTX *c); +// +//go:linkname md5Final C.MD5_Final +func md5Final(md *byte, c *MD5_CTX) c.Int + +func (c *MD5_CTX) Final(md *byte) c.Int { + return md5Final(md, c) +} + +// OSSL_DEPRECATEDIN_3_0 unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md); +// +//go:linkname MD5 C.MD5 +func MD5(data unsafe.Pointer, n uintptr, md *byte) *byte + +func MD5Bytes(data []byte, md *byte) *byte { + return MD5(unsafe.Pointer(unsafe.SliceData(data)), uintptr(len(data)), md) +} + +func MD5String(data string, md *byte) *byte { + return MD5(unsafe.Pointer(unsafe.StringData(data)), uintptr(len(data)), md) +} + +// OSSL_DEPRECATEDIN_3_0 void MD5_Transform(MD5_CTX *c, const unsigned char *b); + +// ----------------------------------------------------------------------------- diff --git a/c/openssl/openssl.go b/c/openssl/openssl.go new file mode 100644 index 00000000..5002e851 --- /dev/null +++ b/c/openssl/openssl.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 openssl + +import ( + "unsafe" + + "github.com/goplus/llgo/c" +) + +// ----------------------------------------------------------------------------- + +const ( + LLGoFiles = "$(pkg-config --cflags openssl): _wrap/openssl.c" + LLGoPackage = "link: $(pkg-config --libs openssl); -lssl -lcrypto" +) + +//go:linkname Free C.opensslFree +func Free(ptr unsafe.Pointer) + +//go:linkname FreeCStr C.opensslFree +func FreeCStr(ptr *c.Char) + +// ----------------------------------------------------------------------------- diff --git a/c/openssl/pem.go b/c/openssl/pem.go new file mode 100644 index 00000000..fb2f360b --- /dev/null +++ b/c/openssl/pem.go @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 openssl + +import ( + "unsafe" + + "github.com/goplus/llgo/c" +) + +// ----------------------------------------------------------------------------- + +// typedef int (*pem_password_cb)(char *buf, int size, int rwflag, void *userdata); +// +// llgo:type C +type PemPasswordCb func(buf *c.Char, size, rwflag c.Int, userdata unsafe.Pointer) c.Int + +// RSA *PEM_read_bio_RSAPrivateKey(BIO *bp, RSA **x, pem_password_cb *cb, void *u); +// +//go:linkname PEMReadBioRSAPrivateKey C.PEM_read_bio_RSAPrivateKey +func PEMReadBioRSAPrivateKey(bp *BIO, x **RSA, cb PemPasswordCb, u unsafe.Pointer) *RSA + +// ----------------------------------------------------------------------------- diff --git a/c/openssl/rand.go b/c/openssl/rand.go new file mode 100644 index 00000000..f9ad6338 --- /dev/null +++ b/c/openssl/rand.go @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 openssl + +import ( + "unsafe" + + "github.com/goplus/llgo/c" +) + +// ----------------------------------------------------------------------------- + +// int RAND_bytes(unsigned char *buf, int num); +// +//go:linkname RANDBufferWithLen C.RAND_bytes +func RANDBufferWithLen(buf *byte, num c.Int) c.Int + +func RANDBytes(buf []byte) c.Int { + return RANDBufferWithLen(unsafe.SliceData(buf), c.Int(len(buf))) +} + +// int RAND_priv_bytes(unsigned char *buf, int num); +// +//go:linkname RANDPrivBufferWithLen C.RAND_priv_bytes +func RANDPrivBufferWithLen(buf *byte, num c.Int) c.Int + +func RANDPrivBytes(buf []byte) c.Int { + return RANDPrivBufferWithLen(unsafe.SliceData(buf), c.Int(len(buf))) +} + +// void RAND_seed(const void *buf, int num); +// +//go:linkname RANDSeed C.RAND_seed +func RANDSeed(buf unsafe.Pointer, num c.Int) + +// void RAND_keep_random_devices_open(int keep); +// +//go:linkname RANDKeepRandomDevicesOpen C.RAND_keep_random_devices_open +func RANDKeepRandomDevicesOpen(keep c.Int) + +// int RAND_load_file(const char *file, long max_bytes); +// +//go:linkname RANDLoadFile C.RAND_load_file +func RANDLoadFile(file *c.Char, maxBytes c.Long) c.Int + +// int RAND_write_file(const char *file); +// +//go:linkname RANDWriteFile C.RAND_write_file +func RANDWriteFile(file *c.Char) c.Int + +// const char *RAND_file_name(char *file, size_t num); +// +//go:linkname RANDFileName C.RAND_file_name +func RANDFileName(file *c.Char, num uintptr) *c.Char + +// int RAND_status(void); +// +//go:linkname RANDStatus C.RAND_status +func RANDStatus() c.Int + +// int RAND_poll(void); +// +//go:linkname RANDPoll C.RAND_poll +func RANDPoll() c.Int + +// ----------------------------------------------------------------------------- diff --git a/c/openssl/rsa.go b/c/openssl/rsa.go new file mode 100644 index 00000000..23c60f28 --- /dev/null +++ b/c/openssl/rsa.go @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 openssl + +import ( + "unsafe" + + "github.com/goplus/llgo/c" +) + +// ----------------------------------------------------------------------------- + +type RSA_METHOD struct { + Unused [0]byte +} + +// OSSL_DEPRECATEDIN_3_0 RSA_METHOD *RSA_meth_new(const char *name, int flags); +// +//go:linkname RSAMethNew C.RSA_meth_new +func RSAMethNew(name *byte, flags c.Int) *RSA_METHOD + +// OSSL_DEPRECATEDIN_3_0 void RSA_meth_free(RSA_METHOD *meth); +// +// llgo:link (*RSA_METHOD).Free C.RSA_meth_free +func (*RSA_METHOD) Free() {} + +// OSSL_DEPRECATEDIN_3_0 +// int RSA_meth_set_init(RSA_METHOD *rsa, int (*init) (RSA *rsa)); +// +// llgo:link (*RSA_METHOD).SetInit C.RSA_meth_set_init +func (*RSA_METHOD) SetInit(init func(rsa *RSA) c.Int) c.Int { return 0 } + +// OSSL_DEPRECATEDIN_3_0 +// int RSA_meth_set_finish(RSA_METHOD *rsa, int (*finish) (RSA *rsa)); +// +// llgo:link (*RSA_METHOD).SetFinish C.RSA_meth_set_finish +func (*RSA_METHOD) SetFinish(finish func(rsa *RSA) c.Int) c.Int { return 0 } + +/* +OSSL_DEPRECATEDIN_3_0 +int RSA_meth_set_mod_exp(RSA_METHOD *rsa, + int (*mod_exp) (BIGNUM *r0, const BIGNUM *i, RSA *rsa, + BN_CTX *ctx)); +*/ +// llgo:link (*RSA_METHOD).SetModExp C.RSA_meth_set_mod_exp +func (*RSA_METHOD) SetModExp(modExp func( + r0 *BIGNUM, i *BIGNUM, rsa *RSA, ctx *BN_CTX) c.Int) c.Int { + return 0 +} + +/* +OSSL_DEPRECATEDIN_3_0 +int RSA_meth_set_bn_mod_exp(RSA_METHOD *rsa, + int (*bn_mod_exp) (BIGNUM *r, + const BIGNUM *a, + const BIGNUM *p, + const BIGNUM *m, + BN_CTX *ctx, + BN_MONT_CTX *m_ctx)); +//-llgo:link (*RSA_METHOD).SetBnModExp C.RSA_meth_set_bn_mod_exp +func (*RSA_METHOD) SetBnModExp(bnModExp func( + r *BIGNUM, a *BIGNUM, p *BIGNUM, m *BIGNUM, ctx *BN_CTX, mCtx *BN_MONT_CTX) c.Int) c.Int { + return 0 +} +*/ + +/* +OSSL_DEPRECATEDIN_3_0 +int RSA_meth_set_pub_enc(RSA_METHOD *rsa, + int (*pub_enc) (int flen, const unsigned char *from, + unsigned char *to, RSA *rsa, + int padding)); +*/ +// llgo:link (*RSA_METHOD).SetPubEnc C.RSA_meth_set_pub_enc +func (*RSA_METHOD) SetPubEnc(pubEnc func( + flen c.Int, from *byte, to *byte, rsa *RSA, padding c.Int) c.Int) c.Int { + return 0 +} + +/* +OSSL_DEPRECATEDIN_3_0 +int RSA_meth_set_pub_dec(RSA_METHOD *rsa, + int (*pub_dec) (int flen, const unsigned char *from, + unsigned char *to, RSA *rsa, + int padding)); +*/ +// llgo:link (*RSA_METHOD).SetPubDec C.RSA_meth_set_pub_dec +func (*RSA_METHOD) SetPubDec(pubDec func( + flen c.Int, from *byte, to *byte, rsa *RSA, padding c.Int) c.Int) c.Int { + return 0 +} + +/* +OSSL_DEPRECATEDIN_3_0 +int RSA_meth_set_priv_enc(RSA_METHOD *rsa, + int (*priv_enc) (int flen, const unsigned char *from, + unsigned char *to, RSA *rsa, + int padding)); +*/ +// llgo:link (*RSA_METHOD).SetPrivEnc C.RSA_meth_set_priv_enc +func (*RSA_METHOD) SetPrivEnc(privEnc func( + flen c.Int, from *byte, to *byte, rsa *RSA, padding c.Int) c.Int) c.Int { + return 0 +} + +/* +OSSL_DEPRECATEDIN_3_0 +int RSA_meth_set_priv_dec(RSA_METHOD *rsa, + int (*priv_dec) (int flen, const unsigned char *from, + unsigned char *to, RSA *rsa, + int padding)); +*/ +// llgo:link (*RSA_METHOD).SetPrivDec C.RSA_meth_set_priv_dec +func (*RSA_METHOD) SetPrivDec(privDec func( + flen c.Int, from *byte, to *byte, rsa *RSA, padding c.Int) c.Int) c.Int { + return 0 +} + +/* +OSSL_DEPRECATEDIN_3_0 +int RSA_meth_set_sign(RSA_METHOD *rsa, + int (*sign) (int type, const unsigned char *m, + unsigned int m_length, + unsigned char *sigret, unsigned int *siglen, + const RSA *rsa)); +*/ +// llgo:link (*RSA_METHOD).SetSign C.RSA_meth_set_sign +func (*RSA_METHOD) SetSign(sign func( + typ c.Int, msg *byte, mlen c.Uint, + sigret *byte, siglen *c.Uint, rsa *RSA) c.Int) c.Int { + return 0 +} + +/* +OSSL_DEPRECATEDIN_3_0 +int RSA_meth_set_verify(RSA_METHOD *rsa, + int (*verify) (int dtype, const unsigned char *m, + unsigned int m_length, + const unsigned char *sigbuf, + unsigned int siglen, const RSA *rsa)); +*/ +// llgo:link (*RSA_METHOD).SetVerify C.RSA_meth_set_verify +func (*RSA_METHOD) SetVerify(verify func( + dtype c.Int, msg *byte, mlen c.Uint, + sigbuf *byte, siglen c.Uint, rsa *RSA) c.Int) c.Int { + return 0 +} + +/* +OSSL_DEPRECATEDIN_3_0 +int RSA_meth_set_keygen(RSA_METHOD *rsa, + int (*keygen) (RSA *rsa, int bits, BIGNUM *e, + BN_GENCB *cb)); + +OSSL_DEPRECATEDIN_3_0 +int RSA_meth_set_multi_prime_keygen(RSA_METHOD *meth, + int (*keygen) (RSA *rsa, int bits, + int primes, BIGNUM *e, + BN_GENCB *cb)); +*/ + +// ----------------------------------------------------------------------------- + +type RSA struct { + Unused [0]byte +} + +// OSSL_DEPRECATEDIN_3_0 RSA *RSA_new(void); +// +//go:linkname RSANew C.RSA_new +func RSANew() *RSA + +// OSSL_DEPRECATEDIN_3_0 RSA *RSA_new_method(ENGINE *engine); + +// OSSL_DEPRECATEDIN_3_0 void RSA_free(RSA *r); +// +// llgo:link (*RSA).Free C.RSA_free +func (*RSA) Free() {} + +// "up" the RSA object's reference count +// OSSL_DEPRECATEDIN_3_0 int RSA_up_ref(RSA *r); +// +// llgo:link (*RSA).UpRef C.RSA_up_ref +func (*RSA) UpRef() c.Int { return 0 } + +// OSSL_DEPRECATEDIN_3_0 int RSA_bits(const RSA *rsa); +// +// llgo:link (*RSA).Bits C.RSA_bits +func (*RSA) Bits() c.Int { return 0 } + +// OSSL_DEPRECATEDIN_3_0 int RSA_size(const RSA *rsa); +// +// llgo:link (*RSA).Size C.RSA_size +func (*RSA) Size() c.Int { return 0 } + +// OSSL_DEPRECATEDIN_3_0 int RSA_security_bits(const RSA *rsa); +// +// llgo:link (*RSA).SecurityBits C.RSA_security_bits +func (*RSA) SecurityBits() c.Int { return 0 } + +// OSSL_DEPRECATEDIN_3_0 int RSA_flags(const RSA *r); +// +// llgo:link (*RSA).Flags C.RSA_flags +func (*RSA) Flags() c.Int { return 0 } + +// OSSL_DEPRECATEDIN_3_0 void RSA_set_flags(RSA *r, int flags); +// +// llgo:link (*RSA).SetFlags C.RSA_set_flags +func (*RSA) SetFlags(flags c.Int) {} + +// OSSL_DEPRECATEDIN_3_0 void RSA_clear_flags(RSA *r, int flags); +// +// llgo:link (*RSA).ClearFlags C.RSA_clear_flags +func (*RSA) ClearFlags(flags c.Int) {} + +// OSSL_DEPRECATEDIN_3_0 int RSA_test_flags(const RSA *r, int flags); +// +// llgo:link (*RSA).TestFlags C.RSA_test_flags +func (*RSA) TestFlags(flags c.Int) c.Int { return 0 } + +// OSSL_DEPRECATEDIN_3_0 int RSA_get_version(RSA *r); +// +// llgo:link (*RSA).GetVersion C.RSA_get_version +func (*RSA) GetVersion() c.Int { return 0 } + +// OSSL_DEPRECATEDIN_3_0 int RSA_set_ex_data(RSA *r, int idx, void *arg); +// +// llgo:link (*RSA).SetExData C.RSA_set_ex_data +func (*RSA) SetExData(idx c.Int, arg unsafe.Pointer) c.Int { return 0 } + +// OSSL_DEPRECATEDIN_3_0 void *RSA_get_ex_data(const RSA *r, int idx); +// +// llgo:link (*RSA).GetExData C.RSA_get_ex_data +func (*RSA) GetExData(idx c.Int) unsafe.Pointer { return nil } + +// OSSL_DEPRECATEDIN_3_0 int RSA_set_method(RSA *rsa, const RSA_METHOD *meth); +// +// llgo:link (*RSA).SetMethod C.RSA_set_method +func (*RSA) SetMethod(meth *RSA_METHOD) c.Int { return 0 } + +// OSSL_DEPRECATEDIN_3_0 int RSA_generate_key_ex(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb); +// +// llgo:link (*RSA).GenerateKeyEx C.RSA_generate_key_ex +func (*RSA) GenerateKeyEx(bits c.Int, e *BIGNUM, cb *BN_GENCB) c.Int { return 0 } + +// OSSL_DEPRECATEDIN_3_0 int RSA_generate_multi_prime_key(RSA *rsa, int bits, int primes, BIGNUM *e, BN_GENCB *cb); +// +// llgo:link (*RSA).GenerateMultiPrimeKey C.RSA_generate_multi_prime_key +func (*RSA) GenerateMultiPrimeKey(bits, primes c.Int, e *BIGNUM, cb *BN_GENCB) c.Int { return 0 } + +/* +// next 4 return -1 on error + +OSSL_DEPRECATEDIN_3_0 +int RSA_public_encrypt(int flen, const unsigned char *from, unsigned char *to, + RSA *rsa, int padding); +OSSL_DEPRECATEDIN_3_0 +int RSA_private_encrypt(int flen, const unsigned char *from, unsigned char *to, + RSA *rsa, int padding); +OSSL_DEPRECATEDIN_3_0 +int RSA_public_decrypt(int flen, const unsigned char *from, unsigned char *to, + RSA *rsa, int padding); +OSSL_DEPRECATEDIN_3_0 +int RSA_private_decrypt(int flen, const unsigned char *from, unsigned char *to, + RSA *rsa, int padding); +*/ +//go:linkname RSAPublicEncrypt C.RSA_public_encrypt +func RSAPublicEncrypt(flen c.Int, from *byte, to *byte, rsa *RSA, padding c.Int) c.Int + +//go:linkname RSAPrivateEncrypt C.RSA_private_encrypt +func RSAPrivateEncrypt(flen c.Int, from *byte, to *byte, rsa *RSA, padding c.Int) c.Int + +//go:linkname RSAPublicDecrypt C.RSA_public_decrypt +func RSAPublicDecrypt(flen c.Int, from *byte, to *byte, rsa *RSA, padding c.Int) c.Int + +//go:linkname RSAPrivateDecrypt C.RSA_private_decrypt +func RSAPrivateDecrypt(flen c.Int, from *byte, to *byte, rsa *RSA, padding c.Int) c.Int + +// OSSL_DEPRECATEDIN_3_0 int RSA_sign( +// int type, const unsigned char *m, unsigned int m_length, +// unsigned char *sigret, unsigned int *siglen, RSA *rsa); +// +//go:linkname RSASign C.RSA_sign +func RSASign(typ c.Int, msg *byte, mlen c.Uint, sigret *byte, siglen *c.Uint, rsa *RSA) c.Int + +// OSSL_DEPRECATEDIN_3_0 int RSA_verify(int type, const unsigned char *m, +// unsigned int m_length, +// const unsigned char *sigbuf, +// unsigned int siglen, RSA *rsa); + +// ----------------------------------------------------------------------------- diff --git a/c/openssl/sha1.go b/c/openssl/sha1.go new file mode 100644 index 00000000..4ad88ca9 --- /dev/null +++ b/c/openssl/sha1.go @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 openssl + +import ( + "unsafe" + + "github.com/goplus/llgo/c" +) + +const ( + SHA_DIGEST_LENGTH = 20 + SHA_LBLOCK = 16 + SHA_CBLOCK = (SHA_LBLOCK * 4) + SHA_LAST_BLOCK = (SHA_CBLOCK - 8) + + SHA256_CBLOCK = (SHA_LBLOCK * 4) + SHA256_192_DIGEST_LENGTH = 24 + SHA224_DIGEST_LENGTH = 28 + SHA256_DIGEST_LENGTH = 32 + SHA384_DIGEST_LENGTH = 48 + SHA512_DIGEST_LENGTH = 64 + SHA512_CBLOCK = (SHA_LBLOCK * 8) +) + +type SHA_LONG64 = c.UlongLong + +type SHA_LONG = c.Uint + +type SHA_CTX struct { + H0, H1, H2, H3, H4 SHA_LONG + Nl, Nh SHA_LONG + Data [SHA_LBLOCK]SHA_LONG + Num c.Uint +} + +// OSSL_DEPRECATEDIN_3_0 int SHA1_Init(SHA_CTX *c); +// +// llgo:link (*SHA_CTX).Init C.SHA1_Init +func (c *SHA_CTX) Init() c.Int { return 0 } + +// OSSL_DEPRECATEDIN_3_0 int SHA1_Update(SHA_CTX *c, const void *data, size_t len); +// +// llgo:link (*SHA_CTX).Update C.SHA1_Update +func (c *SHA_CTX) Update(data unsafe.Pointer, n uintptr) c.Int { return 0 } + +func (c *SHA_CTX) UpdateBytes(data []byte) c.Int { + return c.Update(unsafe.Pointer(unsafe.SliceData(data)), uintptr(len(data))) +} + +func (c *SHA_CTX) UpdateString(data string) c.Int { + return c.Update(unsafe.Pointer(unsafe.StringData(data)), uintptr(len(data))) +} + +// OSSL_DEPRECATEDIN_3_0 int SHA1_Final(unsigned char *md, SHA_CTX *c); +// +//go:linkname sha1Final C.SHA1_Final +func sha1Final(md *byte, c *SHA_CTX) c.Int + +func (c *SHA_CTX) Final(md *byte) c.Int { + return sha1Final(md, c) +} + +// OSSL_DEPRECATEDIN_3_0 void SHA1_Transform(SHA_CTX *c, const unsigned char *data); +// +// llgo:link (*SHA_CTX).Transform C.SHA1_Transform +func (c *SHA_CTX) Transform(data *byte) {} + +// unsigned char *SHA1(const unsigned char *d, size_t n, unsigned char *md); +// +//go:linkname SHA1 C.SHA1 +func SHA1(data unsafe.Pointer, n uintptr, md *byte) *byte + +func SHA1Bytes(data []byte, md *byte) *byte { + return SHA1(unsafe.Pointer(unsafe.SliceData(data)), uintptr(len(data)), md) +} + +func SHA1String(data string, md *byte) *byte { + return SHA1(unsafe.Pointer(unsafe.StringData(data)), uintptr(len(data)), md) +} diff --git a/c/openssl/sha256.go b/c/openssl/sha256.go new file mode 100644 index 00000000..f7bd9f82 --- /dev/null +++ b/c/openssl/sha256.go @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 openssl + +import ( + "unsafe" + + "github.com/goplus/llgo/c" +) + +type SHA256_CTX struct { + H [8]SHA_LONG + Nl, Nh SHA_LONG + Data [SHA_LBLOCK]SHA_LONG + Num, MdLen c.Uint +} + +type SHA224_CTX SHA256_CTX + +// OSSL_DEPRECATEDIN_3_0 int SHA224_Init(SHA256_CTX *c); +// +// llgo:link (*SHA224_CTX).Init C.SHA224_Init +func (c *SHA224_CTX) Init() c.Int { return 0 } + +// OSSL_DEPRECATEDIN_3_0 int SHA224_Update(SHA256_CTX *c, const void *data, size_t len); +// +// llgo:link (*SHA224_CTX).Update C.SHA224_Update +func (c *SHA224_CTX) Update(data unsafe.Pointer, n uintptr) c.Int { return 0 } + +func (c *SHA224_CTX) UpdateBytes(data []byte) c.Int { + return c.Update(unsafe.Pointer(unsafe.SliceData(data)), uintptr(len(data))) +} + +func (c *SHA224_CTX) UpdateString(data string) c.Int { + return c.Update(unsafe.Pointer(unsafe.StringData(data)), uintptr(len(data))) +} + +// OSSL_DEPRECATEDIN_3_0 int SHA224_Final(unsigned char *md, SHA256_CTX *c); +// +//go:linkname sha224Final C.SHA224_Final +func sha224Final(md *byte, c *SHA224_CTX) c.Int + +func (c *SHA224_CTX) Final(md *byte) c.Int { + return sha224Final(md, c) +} + +// OSSL_DEPRECATEDIN_3_0 int SHA256_Init(SHA256_CTX *c); +// +// llgo:link (*SHA256_CTX).Init C.SHA256_Init +func (c *SHA256_CTX) Init() c.Int { return 0 } + +// OSSL_DEPRECATEDIN_3_0 int SHA256_Update(SHA256_CTX *c, const void *data, size_t len); +// +// llgo:link (*SHA256_CTX).Update C.SHA256_Update +func (c *SHA256_CTX) Update(data unsafe.Pointer, n uintptr) c.Int { return 0 } + +func (c *SHA256_CTX) UpdateBytes(data []byte) c.Int { + return c.Update(unsafe.Pointer(unsafe.SliceData(data)), uintptr(len(data))) +} + +func (c *SHA256_CTX) UpdateString(data string) c.Int { + return c.Update(unsafe.Pointer(unsafe.StringData(data)), uintptr(len(data))) +} + +// OSSL_DEPRECATEDIN_3_0 int SHA256_Final(unsigned char *md, SHA256_CTX *c); +// +//go:linkname sha256Final C.SHA256_Final +func sha256Final(md *byte, c *SHA256_CTX) c.Int + +func (c *SHA256_CTX) Final(md *byte) c.Int { + return sha256Final(md, c) +} + +// OSSL_DEPRECATEDIN_3_0 void SHA256_Transform(SHA256_CTX *c, const unsigned char *data); +// +// llgo:link (*SHA256_CTX).Transform C.SHA256_Transform +func (c *SHA256_CTX) Transform(data *byte) {} + +// unsigned char *SHA224(const unsigned char *d, size_t n, unsigned char *md); +// +//go:linkname SHA224 C.SHA224 +func SHA224(data unsafe.Pointer, n uintptr, md *byte) *byte + +func SHA224Bytes(data []byte, md *byte) *byte { + return SHA224(unsafe.Pointer(unsafe.SliceData(data)), uintptr(len(data)), md) +} + +func SHA224String(data string, md *byte) *byte { + return SHA224(unsafe.Pointer(unsafe.StringData(data)), uintptr(len(data)), md) +} + +// unsigned char *SHA256(const unsigned char *d, size_t n, unsigned char *md); +// +//go:linkname SHA256 C.SHA256 +func SHA256(data unsafe.Pointer, n uintptr, md *byte) *byte + +func SHA256Bytes(data []byte, md *byte) *byte { + return SHA256(unsafe.Pointer(unsafe.SliceData(data)), uintptr(len(data)), md) +} + +func SHA256String(data string, md *byte) *byte { + return SHA256(unsafe.Pointer(unsafe.StringData(data)), uintptr(len(data)), md) +} diff --git a/c/openssl/sha512.go b/c/openssl/sha512.go new file mode 100644 index 00000000..ec2e0739 --- /dev/null +++ b/c/openssl/sha512.go @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 openssl + +import ( + "unsafe" + + "github.com/goplus/llgo/c" +) + +type SHA512_CTX struct { + H [8]SHA_LONG64 + N1, Nh SHA_LONG64 + D [SHA_LBLOCK]SHA_LONG64 + Num, MdLen c.Uint +} + +type SHA384_CTX SHA512_CTX + +// OSSL_DEPRECATEDIN_3_0 int SHA384_Init(SHA512_CTX *c); +// +// llgo:link (*SHA384_CTX).Init C.SHA384_Init +func (c *SHA384_CTX) Init() c.Int { return 0 } + +// OSSL_DEPRECATEDIN_3_0 int SHA384_Update(SHA512_CTX *c, const void *data, size_t len); +// +// llgo:link (*SHA384_CTX).Update C.SHA384_Update +func (c *SHA384_CTX) Update(data unsafe.Pointer, n uintptr) c.Int { return 0 } + +func (c *SHA384_CTX) UpdateBytes(data []byte) c.Int { + return c.Update(unsafe.Pointer(unsafe.SliceData(data)), uintptr(len(data))) +} + +func (c *SHA384_CTX) UpdateString(data string) c.Int { + return c.Update(unsafe.Pointer(unsafe.StringData(data)), uintptr(len(data))) +} + +// OSSL_DEPRECATEDIN_3_0 int SHA384_Final(unsigned char *md, SHA512_CTX *c); +// +//go:linkname sha384Final C.SHA384_Final +func sha384Final(md *byte, c *SHA384_CTX) c.Int + +func (c *SHA384_CTX) Final(md *byte) c.Int { + return sha384Final(md, c) +} + +// OSSL_DEPRECATEDIN_3_0 int SHA512_Init(SHA512_CTX *c); +// +// llgo:link (*SHA512_CTX).Init C.SHA512_Init +func (c *SHA512_CTX) Init() c.Int { return 0 } + +// OSSL_DEPRECATEDIN_3_0 int SHA512_Update(SHA512_CTX *c, const void *data, size_t len); +// +// llgo:link (*SHA512_CTX).Update C.SHA512_Update +func (c *SHA512_CTX) Update(data unsafe.Pointer, n uintptr) c.Int { return 0 } +func (c *SHA512_CTX) UpdateBytes(data []byte) c.Int { + return c.Update(unsafe.Pointer(unsafe.SliceData(data)), uintptr(len(data))) +} +func (c *SHA512_CTX) UpdateString(data string) c.Int { + return c.Update(unsafe.Pointer(unsafe.StringData(data)), uintptr(len(data))) +} + +// OSSL_DEPRECATEDIN_3_0 int SHA512_Final(unsigned char *md, SHA512_CTX *c); +// +//go:linkname sha512Final C.SHA512_Final +func sha512Final(md *byte, c *SHA512_CTX) c.Int + +func (c *SHA512_CTX) Final(md *byte) c.Int { + return sha512Final(md, c) +} + +// OSSL_DEPRECATEDIN_3_0 void SHA512_Transform(SHA512_CTX *c, const unsigned char *data); +// +// llgo:link (*SHA512_CTX).Transform C.SHA512_Transform +func (c *SHA512_CTX) Transform(data *byte) {} + +// unsigned char *SHA384(const unsigned char *d, size_t n, unsigned char *md); +// +//go:linkname SHA384 C.SHA384 +func SHA384(data unsafe.Pointer, n uintptr, md *byte) *byte + +func SHA384Bytes(data []byte, md *byte) *byte { + return SHA384(unsafe.Pointer(unsafe.SliceData(data)), uintptr(len(data)), md) +} + +func SHA384String(data string, md *byte) *byte { + return SHA384(unsafe.Pointer(unsafe.StringData(data)), uintptr(len(data)), md) +} + +// unsigned char *SHA512(const unsigned char *d, size_t n, unsigned char *md); +// +//go:linkname SHA512 C.SHA512 +func SHA512(data unsafe.Pointer, n uintptr, md *byte) *byte + +func SHA512Bytes(data []byte, md *byte) *byte { + return SHA512(unsafe.Pointer(unsafe.SliceData(data)), uintptr(len(data)), md) +} + +func SHA512String(data string, md *byte) *byte { + return SHA512(unsafe.Pointer(unsafe.StringData(data)), uintptr(len(data)), md) +} diff --git a/c/pthread/sync/_pthd/pthd.c b/c/pthread/sync/_pthd/pthd.c index e8275a54..f7bc7eb9 100644 --- a/c/pthread/sync/_pthd/pthd.c +++ b/c/pthread/sync/_pthd/pthd.c @@ -5,3 +5,15 @@ pthread_once_t llgoSyncOnceInitVal = PTHREAD_ONCE_INIT; // ----------------------------------------------------------------------------- + +// wrap return type to void +void wrap_pthread_mutex_lock(pthread_mutex_t *mutex) { + pthread_mutex_lock(mutex); +} + +// wrap return type to void +void wrap_pthread_mutex_unlock(pthread_mutex_t *mutex) { + pthread_mutex_unlock(mutex); +} + +// ----------------------------------------------------------------------------- diff --git a/c/pthread/sync/sync.go b/c/pthread/sync/sync.go index 33679a54..b9e45d48 100644 --- a/c/pthread/sync/sync.go +++ b/c/pthread/sync/sync.go @@ -76,13 +76,13 @@ func (m *Mutex) Init(attr *MutexAttr) c.Int { return 0 } // llgo:link (*Mutex).Destroy C.pthread_mutex_destroy func (m *Mutex) Destroy() {} -// llgo:link (*Mutex).Lock C.pthread_mutex_lock -func (m *Mutex) Lock() {} - // llgo:link (*Mutex).TryLock C.pthread_mutex_trylock func (m *Mutex) TryLock() c.Int { return 0 } -// llgo:link (*Mutex).Unlock C.pthread_mutex_unlock +// llgo:link (*Mutex).Lock C.wrap_pthread_mutex_lock +func (m *Mutex) Lock() {} + +// llgo:link (*Mutex).Unlock C.wrap_pthread_mutex_unlock func (m *Mutex) Unlock() {} // ----------------------------------------------------------------------------- diff --git a/c/setjmp/setjmp.go b/c/setjmp/setjmp.go index 89c6745a..c4bccb18 100644 --- a/c/setjmp/setjmp.go +++ b/c/setjmp/setjmp.go @@ -44,9 +44,6 @@ func Longjmp(env *JmpBuf, val c.Int) // ----------------------------------------------------------------------------- -//go:linkname Sigsetjmp C.sigsetjmp -func Sigsetjmp(env *SigjmpBuf, savemask c.Int) c.Int - //go:linkname Siglongjmp C.siglongjmp func Siglongjmp(env *SigjmpBuf, val c.Int) diff --git a/c/setjmp/setjmp_linux.go b/c/setjmp/setjmp_linux.go new file mode 100644 index 00000000..17bad916 --- /dev/null +++ b/c/setjmp/setjmp_linux.go @@ -0,0 +1,12 @@ +//go:build linux + +package setjmp + +import ( + _ "unsafe" + + "github.com/goplus/llgo/c" +) + +//go:linkname Sigsetjmp C.__sigsetjmp +func Sigsetjmp(env *SigjmpBuf, savemask c.Int) c.Int diff --git a/c/setjmp/setjmp_other.go b/c/setjmp/setjmp_other.go new file mode 100644 index 00000000..a83d513d --- /dev/null +++ b/c/setjmp/setjmp_other.go @@ -0,0 +1,12 @@ +//go:build !linux + +package setjmp + +import ( + _ "unsafe" + + "github.com/goplus/llgo/c" +) + +//go:linkname Sigsetjmp C.sigsetjmp +func Sigsetjmp(env *SigjmpBuf, savemask c.Int) c.Int diff --git a/c/syscall/syscall.go b/c/syscall/syscall.go index 69b266ec..e9a08e06 100644 --- a/c/syscall/syscall.go +++ b/c/syscall/syscall.go @@ -17,7 +17,7 @@ package syscall const ( - LLGoPackage = "decl" + LLGoPackage = "noinit" ) type Errno = uintptr @@ -25,3 +25,8 @@ type Errno = uintptr // A Signal is a number describing a process signal. // It implements the os.Signal interface. type Signal = int + +// Unix returns the time stored in ts as seconds plus nanoseconds. +func (ts *Timespec) Unix() (sec int64, nsec int64) { + return int64(ts.Sec), int64(ts.Nsec) +} diff --git a/c/zlib/_demo/crc32demo/crc.go b/c/zlib/_demo/crc32demo/crc.go new file mode 100644 index 00000000..0d0c6843 --- /dev/null +++ b/c/zlib/_demo/crc32demo/crc.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" + + "github.com/goplus/llgo/c/zlib" +) + +func main() { + fmt.Printf("%08x\n", zlib.Crc32ZString(0, "Hello world")) +} diff --git a/c/zlib/zlib.go b/c/zlib/zlib.go index 1cd6111d..0aa1aaad 100644 --- a/c/zlib/zlib.go +++ b/c/zlib/zlib.go @@ -17,7 +17,7 @@ package zlib import ( - _ "unsafe" + "unsafe" "github.com/goplus/llgo/c" ) @@ -76,17 +76,185 @@ const ( DEFLATED = 8 ) +// ----------------------------------------------------------------------------- + +/* +ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen)); + +compressBound() returns an upper bound on the compressed size after +compress() or compress2() on sourceLen bytes. It would be used before a +compress() or compress2() call to allocate the destination buffer. +*/ //go:linkname CompressBound C.compressBound func CompressBound(sourceLen c.Ulong) c.Ulong +/* +ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); + +Compresses the source buffer into the destination buffer. sourceLen is +the byte length of the source buffer. Upon entry, destLen is the total size +of the destination buffer, which must be at least the value returned by +compressBound(sourceLen). Upon exit, destLen is the actual size of the +compressed data. compress() is equivalent to compress2() with a level +parameter of Z_DEFAULT_COMPRESSION. + +compress returns Z_OK if success, Z_MEM_ERROR if there was not +enough memory, Z_BUF_ERROR if there was not enough room in the output +buffer. +*/ //go:linkname Compress C.compress func Compress(dest *byte, destLen *c.Ulong, source *byte, sourceLen c.Ulong) c.Int +/* +ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen, int level)); + +Compresses the source buffer into the destination buffer. The level +parameter has the same meaning as in deflateInit. sourceLen is the byte +length of the source buffer. Upon entry, destLen is the total size of the +destination buffer, which must be at least the value returned by +compressBound(sourceLen). Upon exit, destLen is the actual size of the +compressed data. + +compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough +memory, Z_BUF_ERROR if there was not enough room in the output buffer, +Z_STREAM_ERROR if the level parameter is invalid. +*/ //go:linkname Compress2 C.compress2 func Compress2(dest *byte, destLen *c.Ulong, source *byte, sourceLen c.Ulong, level c.Int) c.Int +/* +ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); + +Decompresses the source buffer into the destination buffer. sourceLen is +the byte length of the source buffer. Upon entry, destLen is the total size +of the destination buffer, which must be large enough to hold the entire +uncompressed data. (The size of the uncompressed data must have been saved +previously by the compressor and transmitted to the decompressor by some +mechanism outside the scope of this compression library.) Upon exit, destLen +is the actual size of the uncompressed data. + +uncompress returns Z_OK if success, Z_MEM_ERROR if there was not +enough memory, Z_BUF_ERROR if there was not enough room in the output +buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. In +the case where there is not enough room, uncompress() will fill the output +buffer with the uncompressed data up to that point. +*/ //go:linkname Uncompress C.uncompress func Uncompress(dest *byte, destLen *c.Ulong, source *byte, sourceLen c.Ulong) c.Int +/* +ZEXTERN int ZEXPORT uncompress2 OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong *sourceLen)); + +Same as uncompress, except that sourceLen is a pointer, where the +length of the source is *sourceLen. On return, *sourceLen is the number of +source bytes consumed. +*/ //go:linkname Uncompress2 C.uncompress2 func Uncompress2(dest *byte, destLen *c.Ulong, source *byte, sourceLen *c.Ulong) c.Int + +// ----------------------------------------------------------------------------- + +/* +ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); + +Update a running CRC-32 with the bytes buf[0..len-1] and return the +updated CRC-32. If buf is Z_NULL, this function returns the required +initial value for the crc. Pre- and post-conditioning (one's complement) is +performed within this function so it shouldn't be done by the application. + +Usage example: + + uLong crc = crc32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + crc = crc32(crc, buffer, length); + } + if (crc != original_crc) error(); +*/ +//go:linkname Crc32 C.crc32 +func Crc32(crc c.Ulong, buf *byte, len c.Uint) c.Ulong + +/* +ZEXTERN uLong ZEXPORT crc32_z OF((uLong adler, const Bytef *buf, z_size_t len)); + +Same as crc32(), but with a size_t length. +*/ +//go:linkname Crc32Z C.crc32_z +func Crc32Z(crc c.Ulong, buf *byte, len uintptr) c.Ulong + +func Crc32ZBytes(crc c.Ulong, buf []byte) c.Ulong { + return Crc32Z(crc, unsafe.SliceData(buf), uintptr(len(buf))) +} + +func Crc32ZString(crc c.Ulong, buf string) c.Ulong { + return Crc32Z(crc, unsafe.StringData(buf), uintptr(len(buf))) +} + +/* +ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2)); + +Combine two CRC-32 check values into one. For two sequences of bytes, +seq1 and seq2 with lengths len1 and len2, CRC-32 check values were +calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32 +check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and +len2. +*/ +//go:linkname Crc32Combine C.crc32_combine +func Crc32Combine(crc1 c.Ulong, crc2 c.Ulong, len2 int64) c.Ulong + +/* +ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); + +Update a running Adler-32 checksum with the bytes buf[0..len-1] and +return the updated checksum. If buf is Z_NULL, this function returns the +required initial value for the checksum. + +An Adler-32 checksum is almost as reliable as a CRC-32 but can be computed +much faster. + +Usage example: + + uLong adler = adler32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + adler = adler32(adler, buffer, length); + } + if (adler != original_adler) error(); +*/ +//go:linkname Adler32 C.adler32 +func Adler32(adler c.Ulong, buf *byte, len c.Uint) c.Ulong + +/* +ZEXTERN uLong ZEXPORT adler32_z OF((uLong adler, const Bytef *buf, z_size_t len)); + +Same as adler32(), but with a size_t length. +*/ +//go:linkname Adler32Z C.adler32_z +func Adler32Z(adler c.Ulong, buf *byte, len uintptr) c.Ulong + +func Adler32ZBytes(adler c.Ulong, buf []byte) c.Ulong { + return Adler32Z(adler, unsafe.SliceData(buf), uintptr(len(buf))) +} + +func Adler32ZString(adler c.Ulong, buf string) c.Ulong { + return Adler32Z(adler, unsafe.StringData(buf), uintptr(len(buf))) +} + +/* +ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, z_off_t len2)); + +Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 +and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for +each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of +seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. Note +that the z_off_t type (like off_t) is a signed integer. If len2 is +negative, the result has no meaning or utility. +*/ +//go:linkname Adler32Combine C.adler32_combine +func Adler32Combine(adler1 c.Ulong, adler2 c.Ulong, len2 int64) c.Ulong + +// ----------------------------------------------------------------------------- diff --git a/chore/_xtool/llcppsigfetch/llcppsigfetch.go b/chore/_xtool/llcppsigfetch/llcppsigfetch.go new file mode 100644 index 00000000..30190a18 --- /dev/null +++ b/chore/_xtool/llcppsigfetch/llcppsigfetch.go @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 main + +func main() { + // TODO(xsw): implement llcppsigfetch tool +} diff --git a/chore/_xtool/llcppsymg/config/config.go b/chore/_xtool/llcppsymg/config/config.go new file mode 100644 index 00000000..3545e25b --- /dev/null +++ b/chore/_xtool/llcppsymg/config/config.go @@ -0,0 +1,60 @@ +package config + +import ( + "errors" + "unsafe" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/cjson" + "github.com/goplus/llgo/chore/llcppg/types" +) + +type Conf struct { + *cjson.JSON + *types.Config +} + +func GetConf(data []byte) (Conf, error) { + parsedConf := cjson.ParseBytes(data) + if parsedConf == nil { + return Conf{}, errors.New("failed to parse config") + } + + config := &types.Config{ + Name: GetStringItem(parsedConf, "name", ""), + CFlags: GetStringItem(parsedConf, "cflags", ""), + Libs: GetStringItem(parsedConf, "libs", ""), + Include: GetStringArrayItem(parsedConf, "include"), + TrimPrefixes: GetStringArrayItem(parsedConf, "trimPrefixes"), + } + + return Conf{ + JSON: parsedConf, + Config: config, + }, nil +} + +func GetString(obj *cjson.JSON) (value string) { + str := obj.GetStringValue() + return unsafe.String((*byte)(unsafe.Pointer(str)), c.Strlen(str)) +} + +func GetStringItem(obj *cjson.JSON, key string, defval string) (value string) { + item := obj.GetObjectItemCaseSensitive(c.AllocaCStr(key)) + if item == nil { + return defval + } + return GetString(item) +} + +func GetStringArrayItem(obj *cjson.JSON, key string) (value []string) { + item := obj.GetObjectItemCaseSensitive(c.AllocaCStr(key)) + if item == nil { + return + } + value = make([]string, item.GetArraySize()) + for i := range value { + value[i] = GetString(item.GetArrayItem(c.Int(i))) + } + return +} diff --git a/chore/_xtool/llcppsymg/llcppsymg.go b/chore/_xtool/llcppsymg/llcppsymg.go new file mode 100644 index 00000000..93c2194d --- /dev/null +++ b/chore/_xtool/llcppsymg/llcppsymg.go @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 main + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strconv" + "strings" + "unsafe" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/cjson" + "github.com/goplus/llgo/chore/_xtool/llcppsymg/config" + "github.com/goplus/llgo/chore/_xtool/llcppsymg/parse" + "github.com/goplus/llgo/chore/llcppg/types" + "github.com/goplus/llgo/cpp/llvm" + "github.com/goplus/llgo/xtool/nm" +) + +func main() { + cfgFile := "llcppg.cfg" + if len(os.Args) > 1 { + cfgFile = os.Args[1] + } + + var data []byte + var err error + if cfgFile == "-" { + data, err = io.ReadAll(os.Stdin) + } else { + data, err = os.ReadFile(cfgFile) + } + check(err) + + conf, err := config.GetConf(data) + check(err) + defer conf.Delete() + + if err != nil { + fmt.Fprintln(os.Stderr, "Failed to parse config file:", cfgFile) + } + symbols, err := parseDylibSymbols(conf.Libs) + + check(err) + + filepaths := generateHeaderFilePath(conf.CFlags, conf.Include) + astInfos, err := parse.ParseHeaderFile(filepaths) + check(err) + + symbolInfo := getCommonSymbols(symbols, astInfos, conf.TrimPrefixes) + + err = genSymbolTableFile(symbolInfo) + check(err) +} + +func check(err error) { + if err != nil { + panic(err) + } +} + +func parseDylibSymbols(lib string) ([]types.CPPSymbol, error) { + dylibPath, err := generateDylibPath(lib) + if err != nil { + return nil, errors.New("failed to generate dylib path") + } + + files, err := nm.New("").List(dylibPath) + if err != nil { + return nil, errors.New("failed to list symbols in dylib") + } + + var symbols []types.CPPSymbol + + for _, file := range files { + for _, sym := range file.Symbols { + demangleName := decodeSymbolName(sym.Name) + symbols = append(symbols, types.CPPSymbol{ + Symbol: sym, + DemangleName: demangleName, + }) + } + } + + return symbols, nil +} + +func generateDylibPath(lib string) (string, error) { + output := lib + libPath := "" + libName := "" + for _, part := range strings.Fields(string(output)) { + if strings.HasPrefix(part, "-L") { + libPath = part[2:] + } else if strings.HasPrefix(part, "-l") { + libName = part[2:] + } + } + + if libPath == "" || libName == "" { + return "", fmt.Errorf("failed to parse pkg-config output: %s", output) + } + + dylibPath := filepath.Join(libPath, "lib"+libName+".dylib") + return dylibPath, nil +} + +func decodeSymbolName(symbolName string) string { + if symbolName == "" { + return "" + } + + demangled := llvm.ItaniumDemangle(symbolName, true) + if demangled == nil { + return symbolName + } + defer c.Free(unsafe.Pointer(demangled)) + + demangleName := c.GoString(demangled) + if demangleName == "" { + return symbolName + } + + decodedName := strings.TrimSpace(demangleName) + decodedName = strings.ReplaceAll(decodedName, + "std::__1::basic_string, std::__1::allocator > const", + "std::string") + + return decodedName +} + +func generateHeaderFilePath(cflags string, files []string) []string { + prefixPath := cflags + prefixPath = strings.TrimPrefix(prefixPath, "-I") + var includePaths []string + for _, file := range files { + includePaths = append(includePaths, filepath.Join(prefixPath, "/"+file)) + } + return includePaths +} + +func getCommonSymbols(dylibSymbols []types.CPPSymbol, astInfoList []types.ASTInformation, prefix []string) []types.SymbolInfo { + var commonSymbols []types.SymbolInfo + functionNameMap := make(map[string]int) + + for _, astInfo := range astInfoList { + for _, dylibSym := range dylibSymbols { + if strings.TrimPrefix(dylibSym.Name, "_") == astInfo.Symbol { + cppName := generateCPPName(astInfo) + functionNameMap[cppName]++ + symbolInfo := types.SymbolInfo{ + Mangle: strings.TrimPrefix(dylibSym.Name, "_"), + CPP: cppName, + Go: generateMangle(astInfo, functionNameMap[cppName], prefix), + } + commonSymbols = append(commonSymbols, symbolInfo) + break + } + } + } + + return commonSymbols +} + +func generateCPPName(astInfo types.ASTInformation) string { + cppName := astInfo.Name + if astInfo.Class != "" { + cppName = astInfo.Class + "::" + astInfo.Name + } + return cppName +} + +func generateMangle(astInfo types.ASTInformation, count int, prefixes []string) string { + astInfo.Class = removePrefix(astInfo.Class, prefixes) + astInfo.Name = removePrefix(astInfo.Name, prefixes) + res := "" + if astInfo.Class != "" { + if astInfo.Class == astInfo.Name { + res = "(*" + astInfo.Class + ")." + "Init" + if count > 1 { + res += "__" + strconv.Itoa(count-1) + } + } else if astInfo.Name == "~"+astInfo.Class { + res = "(*" + astInfo.Class + ")." + "Dispose" + if count > 1 { + res += "__" + strconv.Itoa(count-1) + } + } else { + res = "(*" + astInfo.Class + ")." + astInfo.Name + if count > 1 { + res += "__" + strconv.Itoa(count-1) + } + } + } else { + res = astInfo.Name + if count > 1 { + res += "__" + strconv.Itoa(count-1) + } + } + return res +} + +func removePrefix(str string, prefixes []string) string { + for _, prefix := range prefixes { + if strings.HasPrefix(str, prefix) { + return strings.TrimPrefix(str, prefix) + } + } + return str +} + +func genSymbolTableFile(symbolInfos []types.SymbolInfo) error { + // keep open follow code block can run successfully + for i := range symbolInfos { + println("symbol", symbolInfos[i].Go) + } + + fileName := "llcppg.symb.json" + existingSymbols, err := readExistingSymbolTable(fileName) + if err != nil { + return err + } + + for i := range symbolInfos { + if existingSymbol, exists := existingSymbols[symbolInfos[i].Mangle]; exists { + symbolInfos[i].Go = existingSymbol.Go + } + } + + root := cjson.Array() + defer root.Delete() + + for _, symbol := range symbolInfos { + item := cjson.Object() + item.SetItem(c.Str("mangle"), cjson.String(c.AllocaCStr(symbol.Mangle))) + item.SetItem(c.Str("c++"), cjson.String(c.AllocaCStr(symbol.CPP))) + item.SetItem(c.Str("go"), cjson.String(c.AllocaCStr(symbol.Go))) + root.AddItem(item) + } + + cStr := root.Print() + if cStr == nil { + return errors.New("symbol table is empty") + } + defer c.Free(unsafe.Pointer(cStr)) + + data := unsafe.Slice((*byte)(unsafe.Pointer(cStr)), c.Strlen(cStr)) + + if err := os.WriteFile(fileName, data, 0644); err != nil { + return errors.New("failed to write symbol table file") + } + return nil +} +func readExistingSymbolTable(fileName string) (map[string]types.SymbolInfo, error) { + existingSymbols := make(map[string]types.SymbolInfo) + + if _, err := os.Stat(fileName); err != nil { + return existingSymbols, nil + } + + data, err := os.ReadFile(fileName) + if err != nil { + return nil, errors.New("failed to read symbol table file") + } + + parsedJSON := cjson.ParseBytes(data) + if parsedJSON == nil { + return nil, errors.New("failed to parse JSON") + } + + arraySize := parsedJSON.GetArraySize() + + for i := 0; i < int(arraySize); i++ { + item := parsedJSON.GetArrayItem(c.Int(i)) + if item == nil { + continue + } + symbol := types.SymbolInfo{ + Mangle: config.GetStringItem(item, "mangle", ""), + CPP: config.GetStringItem(item, "c++", ""), + Go: config.GetStringItem(item, "go", ""), + } + existingSymbols[symbol.Mangle] = symbol + } + + return existingSymbols, nil +} diff --git a/chore/_xtool/llcppsymg/parse/parse.go b/chore/_xtool/llcppsymg/parse/parse.go new file mode 100644 index 00000000..5a15d26a --- /dev/null +++ b/chore/_xtool/llcppsymg/parse/parse.go @@ -0,0 +1,153 @@ +package parse + +import ( + "errors" + "strconv" + "unsafe" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/clang" + "github.com/goplus/llgo/chore/llcppg/types" +) + +type Context struct { + namespaceName string + className string + astInfo []types.ASTInformation + currentFile string +} + +func newContext() *Context { + return &Context{ + astInfo: make([]types.ASTInformation, 0), + } +} + +func (c *Context) setNamespaceName(name string) { + c.namespaceName = name +} + +func (c *Context) setClassName(name string) { + c.className = name +} + +func (c *Context) setCurrentFile(filename string) { + c.currentFile = filename +} + +var context = newContext() + +func collectFuncInfo(cursor clang.Cursor) types.ASTInformation { + + info := types.ASTInformation{ + Namespace: context.namespaceName, + Class: context.className, + } + + cursorStr := cursor.String() + symbol := cursor.Mangling() + + info.Name = c.GoString(cursorStr.CStr()) + + info.Symbol = c.GoString(symbol.CStr()) + if len(info.Symbol) >= 1 { + if info.Symbol[0] == '_' { + info.Symbol = info.Symbol[1:] + } + } + + defer symbol.Dispose() + defer cursorStr.Dispose() + + if context.namespaceName != "" { + info.Namespace = context.namespaceName + } + if context.className != "" { + info.Class = context.className + } + + typeStr := cursor.ResultType().String() + defer typeStr.Dispose() + info.ReturnType = c.GoString(typeStr.CStr()) + + info.Parameters = make([]types.Parameter, cursor.NumArguments()) + for i := 0; i < int(cursor.NumArguments()); i++ { + argCurSor := cursor.Argument(c.Uint(i)) + argType := argCurSor.Type().String() + argName := argCurSor.String() + info.Parameters[i] = types.Parameter{ + Name: c.GoString(argName.CStr()), + Type: c.GoString(argType.CStr()), + } + + argType.Dispose() + argName.Dispose() + } + + return info +} + +func visit(cursor, parent clang.Cursor, clientData c.Pointer) clang.ChildVisitResult { + if cursor.Kind == clang.Namespace { + nameStr := cursor.String() + context.setNamespaceName(c.GoString(nameStr.CStr())) + clang.VisitChildren(cursor, visit, nil) + context.setNamespaceName("") + } else if cursor.Kind == clang.ClassDecl { + nameStr := cursor.String() + context.setClassName(c.GoString(nameStr.CStr())) + clang.VisitChildren(cursor, visit, nil) + context.setClassName("") + } else if cursor.Kind == clang.CXXMethod || cursor.Kind == clang.FunctionDecl || cursor.Kind == clang.Constructor || cursor.Kind == clang.Destructor { + loc := cursor.Location() + var file clang.File + var line, column c.Uint + + loc.SpellingLocation(&file, &line, &column, nil) + filename := file.FileName() + + if c.Strcmp(filename.CStr(), c.AllocaCStr(context.currentFile)) == 0 { + info := collectFuncInfo(cursor) + info.Location = c.GoString(filename.CStr()) + ":" + strconv.Itoa(int(line)) + ":" + strconv.Itoa(int(column)) + context.astInfo = append(context.astInfo, info) + } + + defer filename.Dispose() + } + + return clang.ChildVisit_Continue +} + +func ParseHeaderFile(filepaths []string) ([]types.ASTInformation, error) { + + index := clang.CreateIndex(0, 0) + args := make([]*c.Char, 3) + args[0] = c.Str("-x") + args[1] = c.Str("c++") + args[2] = c.Str("-std=c++11") + context = newContext() + + for _, filename := range filepaths { + unit := index.ParseTranslationUnit( + c.AllocaCStr(filename), + unsafe.SliceData(args), 3, + nil, 0, + clang.TranslationUnit_None, + ) + + if unit == nil { + return nil, errors.New("Unable to parse translation unit for file " + filename) + } + + cursor := unit.Cursor() + context.setCurrentFile(filename) + + clang.VisitChildren(cursor, visit, nil) + + unit.Dispose() + } + + index.Dispose() + + return context.astInfo, nil +} diff --git a/chore/gogensig/gogensig.go b/chore/gogensig/gogensig.go new file mode 100644 index 00000000..2079aaa2 --- /dev/null +++ b/chore/gogensig/gogensig.go @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 main + +func main() { + // TODO(xsw): implement gogensig tool +} diff --git a/chore/llcppg/README.md b/chore/llcppg/README.md index 43a3c237..971e0a21 100644 --- a/chore/llcppg/README.md +++ b/chore/llcppg/README.md @@ -17,10 +17,11 @@ If `config-file` is not specified, a `llcppg.cfg` file is used in current direct "INIReader.h", "AnotherHeaderFile.h" ], - "libs": "$(pkg-config --libs inireader)" + "libs": "$(pkg-config --libs inireader)", + "trimPrefixes": ["Ini", "INI"] } ``` ## Design -See [Design of llcppg](design.md). +See [llcppg Design](design.md). diff --git a/chore/llcppg/design.md b/chore/llcppg/design.md index 29247d50..9c6eda54 100644 --- a/chore/llcppg/design.md +++ b/chore/llcppg/design.md @@ -1,4 +1,4 @@ -Design of llcppg +llcppg Design ===== ## Usage @@ -11,13 +11,14 @@ If `config-file` is not specified, a `llcppg.cfg` file is used in current direct ```json { - "name": "inireader", + "name": "inih", "cflags": "$(pkg-config --cflags inireader)", "include": [ "INIReader.h", "AnotherHeaderFile.h" ], - "libs": "$(pkg-config --libs inireader)" + "libs": "$(pkg-config --libs inireader)", + "trimPrefixes": ["Ini", "INI"] } ``` @@ -33,9 +34,10 @@ If `config-file` is not specified, a `llcppg.cfg` file is used in current direct ```sh llcppsymg config-file +llcppsymg - # read config from stdin ``` -It generates symbol tables. Its file format is as follows: +It generates a symbol table file named `llcppg.symb.json`. Its file format is as follows: ```json [ @@ -43,38 +45,60 @@ It generates symbol tables. Its file format is as follows: "mangle": "_ZN9INIReaderC1EPKcm", "c++": "INIReader::INIReader(char const*, unsigned long)", "go": "(*Reader).Init__0" - }, + } ] ``` + +### llcppsigfetch + +```sh +llcppsigfetch config-file +llcppsigfetch - # read config from stdin +``` + +It fetches information of C/C++ symbols and print to stdout. Its format is as follows: + +``` +TODO: see llgo/xtool/clang/ast +``` + +### gogensig + +```sh +gogensig ast-file +gogensig - # read AST from stdin +``` + ## Overall ### Process -1. The Parsing Module reads `config.json` to obtain dynamic libraries, header files, and the package name. After parsing, it writes the generated `common_symbol_info.json` path into `config.json`. -2. The Function Declaration Generation Module reads `config.json` to get the package name, header files, and the previously generated `common_symbol_info.json`. After parsing, it generates the function prototype `func_prototype.json`. -3. Reads the previously generated `func_prototype.json`, stores it as a structure, and uses gogen to generate code based on the structure. +1. The Parsing Module reads `llcppg.cfg` to obtain dynamic libraries, header files, and the package name. After parsing, it writes the generated `llcppg.symb.json` path into `llcppg.cfg`. +2. The Function Declaration Generation Module reads `llcppg.cfg` to get the package name, header files, and the previously generated `llcppg.symb.json`. After parsing, it generates the function prototype `llcppg.function.json`. +3. Reads the previously generated `llcppg.information.json`, stores it as a structure, and uses gogen to generate code based on the structure. ## Parsing Module ### Input -Obtains the paths to header files and dynamic library files by reading the JSON file `config.json`. +Obtains the paths to header files and dynamic library files by reading the JSON file `llcppg.cfg`. ```json { - "PackageName": "inireader", - "HeaderFiles": [ - "/path/to/header/INIReader.h", - "/path/to/header/AnotherHeader.h" + "name": "inih", + "cflags": "$(pkg-config --cflags INIReader)", + "include": [ + "INIReader.h", + "AnotherHeaderFile.h" ], - "DLLFile": "/path/to/lib/libINIReader.dylib", - "JSONFile": "/path/to/json/config.json" + "libs": "$(pkg-config --libs INIReader)", + "trimPrefixes": ["Ini", "INI"] } ``` ```bash -./generate_symbol_table /path/to/config.json +llcppsymg config-file ``` ### Implementation Steps @@ -82,15 +106,15 @@ Obtains the paths to header files and dynamic library files by reading the JSON 1. Parse dylib and store: ```go -// common.go +// types.go type CPPSymbol struct { - Address string - Type string - Name string + Symbol string `json:"symbol"` + Type string `json:"type"` + Name string `json:"name"` } // parser_dylib.go -func ParseDylibSymbols(dylibPath string) ([]common.CPPSymbol, error) +func parseDylibSymbols(lib string) ([]common.CPPSymbol, error) ``` 2. Parse header files and store: @@ -98,48 +122,46 @@ func ParseDylibSymbols(dylibPath string) ([]common.CPPSymbol, error) ```go // common.go type ASTInformation struct { - Namespace string - Class string - Name string - BaseClasses []string - ReturnType string - Location string - Parameters []Parameter - Symbol string + Namespace string `json:"namespace"` + Class string `json:"class"` + Name string `json:"name"` + BaseClasses []string `json:"baseClasses"` + ReturnType string `json:"returnType"` + Location string `json:"location"` + Parameters []Parameter `json:"parameters"` + Symbol string `json:"symbol"` } type Parameter struct { - Name string - Type string + Name string `json:"name"` + Type string `json:"type"` } // parser_ast.go -func ParseHeaderFile(files []string) ([]common.ASTInformation, error) +func parseHeaderFile(config types.Config) ([]common.ASTInformation, error) ``` 3. Cross-reference data from the first two steps to get the final output ```go // common.go -type CommonSymbolInfo struct { - FunctionName string - Symbol string - Location string - UserFunctionName string +type SymbolInfo struct { + Mangle string `json:"mangle"` // C++ Symbol + CPP string `json:"c++"` // C++ function name + Go string `json:"go"` // Go function name } // common_symbols.go -func GetCommonSymbols(dylibSymbols []common.CPPSymbol, astInfoList []common.ASTInformation) []common.CommonSymbolInfo +func getCommonSymbols(dylibSymbols []common.CPPSymbol, astInfoList []common.ASTInformation) []common.SymbolInfo { ``` -4. Generate `common_symbol_info.json` file and store the JSON file path into `config.json` +4. Generate `llcppg.symb.json` file and store the JSON file path into `llcppg.cfg` ```go -// generator.go -func GenerateJSON([]CommonSymbolInfo) +func generateJSON([]CommonSymbolInfo) ``` -5. Example `common_symbol_info.json` file +5. Example `llcppg.symb.json` file ```json { @@ -154,14 +176,14 @@ func GenerateJSON([]CommonSymbolInfo) ### Input -No input required, directly reads the `config.json` file +No input required, directly reads the `llcppg.cfg` file ### Implementation Steps 1. Execute the executable ```bash -./generate_func_decl /path/to/config.json +llcppsigfetch config-file ``` 2. Parse header files @@ -169,25 +191,26 @@ No input required, directly reads the `config.json` file ```go // common.go type ASTInformation struct { - Namespace string - Class string - Name string - BaseClasses []string - ReturnType string - Location string - Parameters []Parameter + Namespace string `json:"namespace"` + Class string `json:"class"` + Name string `json:"name"` + BaseClasses []string `json:"baseClasses"` + ReturnType string `json:"returnType"` + Location string `json:"location"` + Parameters []Parameter `json:"parameters"` + Symbol string `json:"symbol"` } type Parameter struct { - Name string - Type string + Name string `json:"name"` + Type string `json:"type"` } // parser_ast.go func ParseHeaderFile(filePath string) ([]common.ASTInformation, error) ``` -3. Generate the final JSON mapping file `func_prototype.json` +3. Generate the final JSON mapping file `llcppg.information.json` ```go func GenerateJSONFile(info []common.ASTInformation) @@ -195,19 +218,19 @@ func ParseHeaderFile(filePath string) ([]common.ASTInformation, error) ```json { - "FunctionName": "A::B::C", - "Symbol": "_ZN9INIReaderC1ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEE", - "Location": "a.h", - "ReturnType" : "int", - "UserFunctionName": "CFromA", - "Parameters" : [ + "functionName": "A::B::C", + "symbol": "_ZN9INIReaderC1ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEE", + "location": "a.h", + "returnType": "int", + "userFunctionName": "CFromA", + "parameters": [ { - "arg1" : "int" + "arg1": "int" }, { - "arg2" : "*char" + "arg2": "*char" } - ] + ] } ``` @@ -215,14 +238,14 @@ func ParseHeaderFile(filePath string) ([]common.ASTInformation, error) ### Input -No input required, directly reads `func_prototype.json` file +No input required, directly reads `llcppg.information.json` file ### Implementation Steps 1. Execute the executable ```bash -./generate_code /path/to/func_prototype.json +gogensig ast-file ``` 2. Parse JSON file @@ -230,11 +253,11 @@ No input required, directly reads `func_prototype.json` file ```go // common.go type HeaderFileInfo struct { - FunctionName string - Symbol string - Location string - UserFunctionName string - Parameters map[string]string + FunctionName string `json:"functionName"` + Symbol string `json:"symbol"` + Location string `json:"location"` + UserFunctionName string `json:"userFunctionName"` + Parameters map[string]string `json:"parameters"` } // parse_json.go diff --git a/chore/llcppg/llcppg.go b/chore/llcppg/llcppg.go new file mode 100644 index 00000000..e0e9745d --- /dev/null +++ b/chore/llcppg/llcppg.go @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os" + "os/exec" + + "github.com/goplus/llgo/chore/llcppg/types" + "github.com/goplus/llgo/xtool/env" +) + +func llcppsymg(conf []byte) error { + cmd := exec.Command("llcppsymg", "-") + cmd.Stdin = bytes.NewReader(conf) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +func llcppsigfetch(conf []byte, out io.Writer) { + cmd := exec.Command("llcppsigfetch", "-") + cmd.Stdin = bytes.NewReader(conf) + cmd.Stdout = out + cmd.Stderr = os.Stderr + err := cmd.Run() + check(err) +} + +func gogensig(in io.Reader) error { + cmd := exec.Command("gogensig", "-") + cmd.Stdin = in + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +func main() { + cfgFile := "llcppg.cfg" + if len(os.Args) > 1 { + cfgFile = os.Args[1] + } + if cfgFile == "-h" || cfgFile == "--help" { + fmt.Fprintln(os.Stderr, "Usage: llcppg [config-file]") + return + } + + f, err := os.Open(cfgFile) + check(err) + defer f.Close() + + var conf types.Config + json.NewDecoder(f).Decode(&conf) + conf.CFlags = env.ExpandEnv(conf.CFlags) + conf.Libs = env.ExpandEnv(conf.Libs) + + b, err := json.MarshalIndent(&conf, "", " ") + check(err) + + err = llcppsymg(b) + check(err) + + r, w := io.Pipe() + go llcppsigfetch(b, w) + + err = gogensig(r) + check(err) +} + +func check(err error) { + if err != nil { + panic(err) + } +} diff --git a/chore/llcppg/types/types.go b/chore/llcppg/types/types.go new file mode 100644 index 00000000..9d700881 --- /dev/null +++ b/chore/llcppg/types/types.go @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 types + +import ( + "github.com/goplus/llgo/xtool/nm" +) + +// Config represents a configuration for the llcppg tool. +type Config struct { + Name string `json:"name"` + CFlags string `json:"cflags"` + Libs string `json:"libs"` + Include []string `json:"include"` + TrimPrefixes []string `json:"trimPrefixes"` +} + +type CPPSymbol struct { + DemangleName string + *nm.Symbol +} + +type ASTInformation struct { + Namespace string `json:"namespace"` + Class string `json:"class"` + Name string `json:"name"` + BaseClasses []string `json:"baseClasses"` + ReturnType string `json:"returnType"` + Location string `json:"location"` + Parameters []Parameter `json:"parameters"` + Symbol string `json:"symbol"` +} + +type Parameter struct { + Name string `json:"name"` + Type string `json:"type"` +} + +type SymbolInfo struct { + Mangle string `json:"mangle"` // C++ Symbol + CPP string `json:"c++"` // C++ function name + Go string `json:"go"` // Go function name +} diff --git a/chore/nmindex/nmindex.go b/chore/nmindex/nmindex.go index 43d34118..fc575fc2 100644 --- a/chore/nmindex/nmindex.go +++ b/chore/nmindex/nmindex.go @@ -21,7 +21,7 @@ import ( "os" "github.com/goplus/llgo/xtool/env/llvm" - "github.com/goplus/llgo/xtool/nm" + "github.com/goplus/llgo/xtool/nm/nmindex" ) func main() { @@ -58,7 +58,7 @@ func makeIndex() { idxDir := indexDir() os.MkdirAll(idxDir, 0755) - b := nm.NewIndexBuilder(env.Nm()) + b := nmindex.NewIndexBuilder(env.Nm()) libDirs := []string{ usrLib(false), usrLib(true), @@ -78,7 +78,7 @@ func query(q string) { q = "_" + q } } - files, err := nm.Query(indexDir(), q) + files, err := nmindex.Query(indexDir(), q) check(err) for _, f := range files { fmt.Printf("%s:\n", f.ArFile) diff --git a/cl/_testgo/closure/out.ll b/cl/_testgo/closure/out.ll index b387030b..652eefd1 100644 --- a/cl/_testgo/closure/out.ll +++ b/cl/_testgo/closure/out.ll @@ -2,6 +2,7 @@ source_filename = "main" %"github.com/goplus/llgo/internal/runtime.String" = type { ptr, i64 } +%main.T = type { ptr, ptr } @"main.init$guard" = global i1 false, align 1 @__llgo_argc = global i32 0, align 4 @@ -37,12 +38,12 @@ _llgo_0: store i64 3, ptr %5, align 4 %6 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %3, align 8 store %"github.com/goplus/llgo/internal/runtime.String" %6, ptr %2, align 8 - %7 = alloca { ptr, ptr }, align 8 - %8 = getelementptr inbounds { ptr, ptr }, ptr %7, i32 0, i32 0 + %7 = alloca %main.T, align 8 + %8 = getelementptr inbounds %main.T, ptr %7, i32 0, i32 0 store ptr @"__llgo_stub.main.main$1", ptr %8, align 8 - %9 = getelementptr inbounds { ptr, ptr }, ptr %7, i32 0, i32 1 + %9 = getelementptr inbounds %main.T, ptr %7, i32 0, i32 1 store ptr null, ptr %9, align 8 - %10 = load { ptr, ptr }, ptr %7, align 8 + %10 = load %main.T, ptr %7, align 8 %11 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 8) %12 = getelementptr inbounds { ptr }, ptr %11, i32 0, i32 0 store ptr %2, ptr %12, align 8 @@ -52,12 +53,15 @@ _llgo_0: %15 = getelementptr inbounds { ptr, ptr }, ptr %13, i32 0, i32 1 store ptr %11, ptr %15, align 8 %16 = load { ptr, ptr }, ptr %13, align 8 - %17 = extractvalue { ptr, ptr } %10, 1 - %18 = extractvalue { ptr, ptr } %10, 0 - call void %18(ptr %17, i64 100) - %19 = extractvalue { ptr, ptr } %16, 1 - %20 = extractvalue { ptr, ptr } %16, 0 - call void %20(ptr %19, i64 200) + %17 = alloca %main.T, align 8 + store { ptr, ptr } %16, ptr %17, align 8 + %18 = load %main.T, ptr %17, align 8 + %19 = extractvalue %main.T %10, 1 + %20 = extractvalue %main.T %10, 0 + call void %20(ptr %19, i64 100) + %21 = extractvalue %main.T %18, 1 + %22 = extractvalue %main.T %18, 0 + call void %22(ptr %21, i64 200) ret i32 0 } diff --git a/cl/_testgo/defer5/in.go b/cl/_testgo/defer5/in.go new file mode 100644 index 00000000..114e7fb9 --- /dev/null +++ b/cl/_testgo/defer5/in.go @@ -0,0 +1,17 @@ +package main + +func main() { + defer println("A") + defer func() { + if e := recover(); e != nil { + println("in defer 1") + panic("panic in defer 1") + } + }() + defer func() { + println("in defer 2") + panic("panic in defer 2") + }() + defer println("B") + panic("panic in main") +} diff --git a/cl/_testgo/defer5/out.ll b/cl/_testgo/defer5/out.ll new file mode 100644 index 00000000..1c8a0e79 --- /dev/null +++ b/cl/_testgo/defer5/out.ll @@ -0,0 +1 @@ +; \ No newline at end of file diff --git a/cl/_testgo/invoke/out.ll b/cl/_testgo/invoke/out.ll index 03a2ae86..b1222dc0 100644 --- a/cl/_testgo/invoke/out.ll +++ b/cl/_testgo/invoke/out.ll @@ -304,13 +304,13 @@ _llgo_0: %14 = getelementptr inbounds %main.T5, ptr %13, i32 0, i32 0 store i64 300, ptr %14, align 4 %15 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 16) - %16 = alloca { ptr, ptr }, align 8 - %17 = getelementptr inbounds { ptr, ptr }, ptr %16, i32 0, i32 0 + %16 = alloca %main.T6, align 8 + %17 = getelementptr inbounds %main.T6, ptr %16, i32 0, i32 0 store ptr @"__llgo_stub.main.main$1", ptr %17, align 8 - %18 = getelementptr inbounds { ptr, ptr }, ptr %16, i32 0, i32 1 + %18 = getelementptr inbounds %main.T6, ptr %16, i32 0, i32 1 store ptr null, ptr %18, align 8 - %19 = load { ptr, ptr }, ptr %16, align 8 - store { ptr, ptr } %19, ptr %15, align 8 + %19 = load %main.T6, ptr %16, align 8 + store %main.T6 %19, ptr %15, align 8 %20 = load %main.T, ptr %2, align 8 %21 = load ptr, ptr @_llgo_main.T, align 8 %22 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 16) diff --git a/cl/_testlibc/demangle/in.go b/cl/_testlibc/demangle/in.go new file mode 100644 index 00000000..d1b0d591 --- /dev/null +++ b/cl/_testlibc/demangle/in.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/cpp/llvm" +) + +func main() { + mangledName := "__ZNK9INIReader10ParseErrorEv" + if name := llvm.ItaniumDemangle(mangledName, true); name != nil { + c.Printf(c.Str("%s\n"), name) + } else { + println("Failed to demangle") + } +} diff --git a/cl/_testlibc/demangle/out.ll b/cl/_testlibc/demangle/out.ll new file mode 100644 index 00000000..a3886f6a --- /dev/null +++ b/cl/_testlibc/demangle/out.ll @@ -0,0 +1,69 @@ +; ModuleID = 'main' +source_filename = "main" + +%"github.com/goplus/llgo/internal/runtime.String" = type { ptr, i64 } + +@"main.init$guard" = global i1 false, align 1 +@__llgo_argc = global i32 0, align 4 +@__llgo_argv = global ptr null, align 8 +@0 = private unnamed_addr constant [29 x i8] c"__ZNK9INIReader10ParseErrorEv", align 1 +@1 = private unnamed_addr constant [4 x i8] c"%s\0A\00", align 1 +@2 = private unnamed_addr constant [18 x i8] c"Failed to demangle", align 1 + +define void @main.init() { +_llgo_0: + %0 = load i1, ptr @"main.init$guard", align 1 + br i1 %0, label %_llgo_2, label %_llgo_1 + +_llgo_1: ; preds = %_llgo_0 + store i1 true, ptr @"main.init$guard", align 1 + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + ret void +} + +define i32 @main(i32 %0, ptr %1) { +_llgo_0: + store i32 %0, ptr @__llgo_argc, align 4 + store ptr %1, ptr @__llgo_argv, align 8 + call void @"github.com/goplus/llgo/internal/runtime.init"() + call void @main.init() + %2 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %3 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %2, i32 0, i32 0 + store ptr @0, ptr %3, align 8 + %4 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %2, i32 0, i32 1 + store i64 29, ptr %4, align 4 + %5 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %2, align 8 + %6 = call ptr @_ZN4llvm15itaniumDemangleENSt3__117basic_string_viewIcNS0_11char_traitsIcEEEEb(%"github.com/goplus/llgo/internal/runtime.String" %5, i1 true) + %7 = icmp ne ptr %6, null + br i1 %7, label %_llgo_1, label %_llgo_3 + +_llgo_1: ; preds = %_llgo_0 + %8 = call i32 (ptr, ...) @printf(ptr @1, ptr %6) + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_3, %_llgo_1 + ret i32 0 + +_llgo_3: ; preds = %_llgo_0 + %9 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %10 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %9, i32 0, i32 0 + store ptr @2, ptr %10, align 8 + %11 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %9, i32 0, i32 1 + store i64 18, ptr %11, align 4 + %12 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %9, align 8 + call void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String" %12) + call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 10) + br label %_llgo_2 +} + +declare void @"github.com/goplus/llgo/internal/runtime.init"() + +declare ptr @_ZN4llvm15itaniumDemangleENSt3__117basic_string_viewIcNS0_11char_traitsIcEEEEb(%"github.com/goplus/llgo/internal/runtime.String", i1) + +declare i32 @printf(ptr, ...) + +declare void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String") + +declare void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8) diff --git a/cl/_testrt/closureconv/in.go b/cl/_testrt/closureconv/in.go new file mode 100644 index 00000000..0b80ae37 --- /dev/null +++ b/cl/_testrt/closureconv/in.go @@ -0,0 +1,63 @@ +package main + +type Func func(a int, b int) int +type Func2 func(a int, b int) int + +type Call struct { + fn Func + n int +} + +func (c *Call) add(a int, b int) int { + return a + b + c.n +} + +func add(a int, b int) int { + return a + b +} + +func demo1(n int) Func { + m := &Call{n: n} + m.fn = m.add + return m.fn +} + +func demo2() Func { + m := &Call{} + return m.add +} + +func demo3() Func { + return add +} + +func demo4() Func { + return func(a, b int) int { return a + b } +} + +func demo5(n int) Func { + return func(a, b int) int { return a + b + n } +} + +func main() { + n1 := demo1(1)(99, 200) + println(n1) + + n2 := demo2()(100, 200) + println(n2) + + n3 := demo3()(100, 200) + println(n3) + + n4 := demo4()(100, 200) + println(n4) + + n5 := demo5(1)(99, 200) + println(n5) + + var fn func(a int, b int) int = demo5(1) + println(fn(99, 200)) + + var fn2 Func2 = (Func2)(demo5(1)) + println(fn2(99, 200)) +} diff --git a/cl/_testrt/closureconv/out.ll b/cl/_testrt/closureconv/out.ll new file mode 100644 index 00000000..f69440d5 --- /dev/null +++ b/cl/_testrt/closureconv/out.ll @@ -0,0 +1,220 @@ +; ModuleID = 'main' +source_filename = "main" + +%main.Call = type { %main.Func, i64 } +%main.Func = type { ptr, ptr } + +@"main.init$guard" = global i1 false, align 1 +@__llgo_argc = global i32 0, align 4 +@__llgo_argv = global ptr null, align 8 + +define i64 @"main.(*Call).add"(ptr %0, i64 %1, i64 %2) { +_llgo_0: + %3 = add i64 %1, %2 + %4 = getelementptr inbounds %main.Call, ptr %0, i32 0, i32 1 + %5 = load i64, ptr %4, align 4 + %6 = add i64 %3, %5 + ret i64 %6 +} + +define i64 @main.add(i64 %0, i64 %1) { +_llgo_0: + %2 = add i64 %0, %1 + ret i64 %2 +} + +define %main.Func @main.demo1(i64 %0) { +_llgo_0: + %1 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 24) + %2 = getelementptr inbounds %main.Call, ptr %1, i32 0, i32 1 + store i64 %0, ptr %2, align 4 + %3 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 8) + %4 = getelementptr inbounds { ptr }, ptr %3, i32 0, i32 0 + store ptr %1, ptr %4, align 8 + %5 = alloca { ptr, ptr }, align 8 + %6 = getelementptr inbounds { ptr, ptr }, ptr %5, i32 0, i32 0 + store ptr @"main.add$bound", ptr %6, align 8 + %7 = getelementptr inbounds { ptr, ptr }, ptr %5, i32 0, i32 1 + store ptr %3, ptr %7, align 8 + %8 = load { ptr, ptr }, ptr %5, align 8 + %9 = getelementptr inbounds %main.Call, ptr %1, i32 0, i32 0 + %10 = alloca %main.Func, align 8 + store { ptr, ptr } %8, ptr %10, align 8 + %11 = load %main.Func, ptr %10, align 8 + store %main.Func %11, ptr %9, align 8 + %12 = getelementptr inbounds %main.Call, ptr %1, i32 0, i32 0 + %13 = load %main.Func, ptr %12, align 8 + ret %main.Func %13 +} + +define %main.Func @main.demo2() { +_llgo_0: + %0 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 24) + %1 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 8) + %2 = getelementptr inbounds { ptr }, ptr %1, i32 0, i32 0 + store ptr %0, ptr %2, align 8 + %3 = alloca { ptr, ptr }, align 8 + %4 = getelementptr inbounds { ptr, ptr }, ptr %3, i32 0, i32 0 + store ptr @"main.add$bound", ptr %4, align 8 + %5 = getelementptr inbounds { ptr, ptr }, ptr %3, i32 0, i32 1 + store ptr %1, ptr %5, align 8 + %6 = load { ptr, ptr }, ptr %3, align 8 + %7 = alloca %main.Func, align 8 + store { ptr, ptr } %6, ptr %7, align 8 + %8 = load %main.Func, ptr %7, align 8 + ret %main.Func %8 +} + +define %main.Func @main.demo3() { +_llgo_0: + %0 = alloca %main.Func, align 8 + %1 = getelementptr inbounds %main.Func, ptr %0, i32 0, i32 0 + store ptr @__llgo_stub.main.add, ptr %1, align 8 + %2 = getelementptr inbounds %main.Func, ptr %0, i32 0, i32 1 + store ptr null, ptr %2, align 8 + %3 = load %main.Func, ptr %0, align 8 + ret %main.Func %3 +} + +define %main.Func @main.demo4() { +_llgo_0: + %0 = alloca %main.Func, align 8 + %1 = getelementptr inbounds %main.Func, ptr %0, i32 0, i32 0 + store ptr @"__llgo_stub.main.demo4$1", ptr %1, align 8 + %2 = getelementptr inbounds %main.Func, ptr %0, i32 0, i32 1 + store ptr null, ptr %2, align 8 + %3 = load %main.Func, ptr %0, align 8 + ret %main.Func %3 +} + +define i64 @"main.demo4$1"(i64 %0, i64 %1) { +_llgo_0: + %2 = add i64 %0, %1 + ret i64 %2 +} + +define %main.Func @main.demo5(i64 %0) { +_llgo_0: + %1 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 8) + store i64 %0, ptr %1, align 4 + %2 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 8) + %3 = getelementptr inbounds { ptr }, ptr %2, i32 0, i32 0 + store ptr %1, ptr %3, align 8 + %4 = alloca { ptr, ptr }, align 8 + %5 = getelementptr inbounds { ptr, ptr }, ptr %4, i32 0, i32 0 + store ptr @"main.demo5$1", ptr %5, align 8 + %6 = getelementptr inbounds { ptr, ptr }, ptr %4, i32 0, i32 1 + store ptr %2, ptr %6, align 8 + %7 = load { ptr, ptr }, ptr %4, align 8 + %8 = alloca %main.Func, align 8 + store { ptr, ptr } %7, ptr %8, align 8 + %9 = load %main.Func, ptr %8, align 8 + ret %main.Func %9 +} + +define i64 @"main.demo5$1"(ptr %0, i64 %1, i64 %2) { +_llgo_0: + %3 = add i64 %1, %2 + %4 = load { ptr }, ptr %0, align 8 + %5 = extractvalue { ptr } %4, 0 + %6 = load i64, ptr %5, align 4 + %7 = add i64 %3, %6 + ret i64 %7 +} + +define void @main.init() { +_llgo_0: + %0 = load i1, ptr @"main.init$guard", align 1 + br i1 %0, label %_llgo_2, label %_llgo_1 + +_llgo_1: ; preds = %_llgo_0 + store i1 true, ptr @"main.init$guard", align 1 + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + ret void +} + +define i32 @main(i32 %0, ptr %1) { +_llgo_0: + store i32 %0, ptr @__llgo_argc, align 4 + store ptr %1, ptr @__llgo_argv, align 8 + call void @"github.com/goplus/llgo/internal/runtime.init"() + call void @main.init() + %2 = call %main.Func @main.demo1(i64 1) + %3 = extractvalue %main.Func %2, 1 + %4 = extractvalue %main.Func %2, 0 + %5 = call i64 %4(ptr %3, i64 99, i64 200) + call void @"github.com/goplus/llgo/internal/runtime.PrintInt"(i64 %5) + call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 10) + %6 = call %main.Func @main.demo2() + %7 = extractvalue %main.Func %6, 1 + %8 = extractvalue %main.Func %6, 0 + %9 = call i64 %8(ptr %7, i64 100, i64 200) + call void @"github.com/goplus/llgo/internal/runtime.PrintInt"(i64 %9) + call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 10) + %10 = call %main.Func @main.demo3() + %11 = extractvalue %main.Func %10, 1 + %12 = extractvalue %main.Func %10, 0 + %13 = call i64 %12(ptr %11, i64 100, i64 200) + call void @"github.com/goplus/llgo/internal/runtime.PrintInt"(i64 %13) + call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 10) + %14 = call %main.Func @main.demo4() + %15 = extractvalue %main.Func %14, 1 + %16 = extractvalue %main.Func %14, 0 + %17 = call i64 %16(ptr %15, i64 100, i64 200) + call void @"github.com/goplus/llgo/internal/runtime.PrintInt"(i64 %17) + call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 10) + %18 = call %main.Func @main.demo5(i64 1) + %19 = extractvalue %main.Func %18, 1 + %20 = extractvalue %main.Func %18, 0 + %21 = call i64 %20(ptr %19, i64 99, i64 200) + call void @"github.com/goplus/llgo/internal/runtime.PrintInt"(i64 %21) + call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 10) + %22 = call %main.Func @main.demo5(i64 1) + %23 = alloca { ptr, ptr }, align 8 + store %main.Func %22, ptr %23, align 8 + %24 = load { ptr, ptr }, ptr %23, align 8 + %25 = extractvalue { ptr, ptr } %24, 1 + %26 = extractvalue { ptr, ptr } %24, 0 + %27 = call i64 %26(ptr %25, i64 99, i64 200) + call void @"github.com/goplus/llgo/internal/runtime.PrintInt"(i64 %27) + call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 10) + %28 = call %main.Func @main.demo5(i64 1) + %29 = extractvalue %main.Func %28, 1 + %30 = extractvalue %main.Func %28, 0 + %31 = call i64 %30(ptr %29, i64 99, i64 200) + call void @"github.com/goplus/llgo/internal/runtime.PrintInt"(i64 %31) + call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 10) + ret i32 0 +} + +declare ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64) + +define i64 @"main.add$bound"(ptr %0, i64 %1, i64 %2) { +_llgo_0: + %3 = load { ptr }, ptr %0, align 8 + %4 = extractvalue { ptr } %3, 0 + %5 = call i64 @"main.(*Call).add"(ptr %4, i64 %1, i64 %2) + ret i64 %5 +} + +declare ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64) + +define linkonce i64 @__llgo_stub.main.add(ptr %0, i64 %1, i64 %2) { +_llgo_0: + %3 = tail call i64 @main.add(i64 %1, i64 %2) + ret i64 %3 +} + +define linkonce i64 @"__llgo_stub.main.demo4$1"(ptr %0, i64 %1, i64 %2) { +_llgo_0: + %3 = tail call i64 @"main.demo4$1"(i64 %1, i64 %2) + ret i64 %3 +} + +declare void @"github.com/goplus/llgo/internal/runtime.init"() + +declare void @"github.com/goplus/llgo/internal/runtime.PrintInt"(i64) + +declare void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8) diff --git a/cl/_testrt/makemap/in.go b/cl/_testrt/makemap/in.go index 4cf5ce72..ff44ce64 100644 --- a/cl/_testrt/makemap/in.go +++ b/cl/_testrt/makemap/in.go @@ -6,6 +6,7 @@ func main() { make3() make4() make5() + make6() } func make1() { @@ -101,3 +102,14 @@ func make5() { println(k, v) } } + +type M map[int]string + +func make6() { + var m M + m = make(map[int]string) + m[1] = "hello" + for k, v := range m { + println(k, v) + } +} diff --git a/cl/_testrt/makemap/out.ll b/cl/_testrt/makemap/out.ll index d0a6ad31..dd5a8217 100644 --- a/cl/_testrt/makemap/out.ll +++ b/cl/_testrt/makemap/out.ll @@ -46,6 +46,8 @@ source_filename = "main" @"chan _llgo_int" = linkonce global ptr null, align 8 @19 = private unnamed_addr constant [4 x i8] c"chan", align 1 @"map[chan _llgo_int]_llgo_int" = linkonce global ptr null, align 8 +@_llgo_main.M = linkonce global ptr null, align 8 +@20 = private unnamed_addr constant [1 x i8] c"M", align 1 define void @main.init() { _llgo_0: @@ -72,6 +74,7 @@ _llgo_0: call void @main.make3() call void @main.make4() call void @main.make5() + call void @main.make6() ret i32 0 } @@ -998,6 +1001,74 @@ _llgo_6: ; preds = %_llgo_5, %_llgo_4 br i1 %41, label %_llgo_2, label %_llgo_3 } +define void @main.make6() { +_llgo_0: + %0 = load ptr, ptr @"map[_llgo_int]_llgo_string", align 8 + %1 = call ptr @"github.com/goplus/llgo/internal/runtime.MakeMap"(ptr %0, i64 0) + %2 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %3 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %2, i32 0, i32 0 + store ptr @5, ptr %3, align 8 + %4 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %2, i32 0, i32 1 + store i64 5, ptr %4, align 4 + %5 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %2, align 8 + %6 = load ptr, ptr @_llgo_main.M, align 8 + %7 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 8) + store i64 1, ptr %7, align 4 + %8 = call ptr @"github.com/goplus/llgo/internal/runtime.MapAssign"(ptr %6, ptr %1, ptr %7) + store %"github.com/goplus/llgo/internal/runtime.String" %5, ptr %8, align 8 + %9 = load ptr, ptr @_llgo_main.M, align 8 + %10 = call ptr @"github.com/goplus/llgo/internal/runtime.NewMapIter"(ptr %9, ptr %1) + br label %_llgo_1 + +_llgo_1: ; preds = %_llgo_2, %_llgo_0 + %11 = call { i1, ptr, ptr } @"github.com/goplus/llgo/internal/runtime.MapIterNext"(ptr %10) + %12 = extractvalue { i1, ptr, ptr } %11, 0 + br i1 %12, label %_llgo_4, label %_llgo_5 + +_llgo_2: ; preds = %_llgo_6 + %13 = extractvalue { i1, i64, %"github.com/goplus/llgo/internal/runtime.String" } %29, 1 + %14 = extractvalue { i1, i64, %"github.com/goplus/llgo/internal/runtime.String" } %29, 2 + call void @"github.com/goplus/llgo/internal/runtime.PrintInt"(i64 %13) + call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 32) + call void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String" %14) + call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 10) + br label %_llgo_1 + +_llgo_3: ; preds = %_llgo_6 + ret void + +_llgo_4: ; preds = %_llgo_1 + %15 = extractvalue { i1, ptr, ptr } %11, 1 + %16 = extractvalue { i1, ptr, ptr } %11, 2 + %17 = load i64, ptr %15, align 4 + %18 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %16, align 8 + %19 = alloca { i1, i64, %"github.com/goplus/llgo/internal/runtime.String" }, align 8 + %20 = getelementptr inbounds { i1, i64, %"github.com/goplus/llgo/internal/runtime.String" }, ptr %19, i32 0, i32 0 + store i1 true, ptr %20, align 1 + %21 = getelementptr inbounds { i1, i64, %"github.com/goplus/llgo/internal/runtime.String" }, ptr %19, i32 0, i32 1 + store i64 %17, ptr %21, align 4 + %22 = getelementptr inbounds { i1, i64, %"github.com/goplus/llgo/internal/runtime.String" }, ptr %19, i32 0, i32 2 + store %"github.com/goplus/llgo/internal/runtime.String" %18, ptr %22, align 8 + %23 = load { i1, i64, %"github.com/goplus/llgo/internal/runtime.String" }, ptr %19, align 8 + br label %_llgo_6 + +_llgo_5: ; preds = %_llgo_1 + %24 = alloca { i1, i64, %"github.com/goplus/llgo/internal/runtime.String" }, align 8 + %25 = getelementptr inbounds { i1, i64, %"github.com/goplus/llgo/internal/runtime.String" }, ptr %24, i32 0, i32 0 + store i1 false, ptr %25, align 1 + %26 = getelementptr inbounds { i1, i64, %"github.com/goplus/llgo/internal/runtime.String" }, ptr %24, i32 0, i32 1 + store i64 0, ptr %26, align 4 + %27 = getelementptr inbounds { i1, i64, %"github.com/goplus/llgo/internal/runtime.String" }, ptr %24, i32 0, i32 2 + store %"github.com/goplus/llgo/internal/runtime.String" zeroinitializer, ptr %27, align 8 + %28 = load { i1, i64, %"github.com/goplus/llgo/internal/runtime.String" }, ptr %24, align 8 + br label %_llgo_6 + +_llgo_6: ; preds = %_llgo_5, %_llgo_4 + %29 = phi { i1, i64, %"github.com/goplus/llgo/internal/runtime.String" } [ %23, %_llgo_4 ], [ %28, %_llgo_5 ] + %30 = extractvalue { i1, i64, %"github.com/goplus/llgo/internal/runtime.String" } %29, 0 + br i1 %30, label %_llgo_2, label %_llgo_3 +} + declare void @"github.com/goplus/llgo/internal/runtime.init"() define void @"main.init$after"() { @@ -1697,6 +1768,37 @@ _llgo_37: ; preds = %_llgo_36 br label %_llgo_38 _llgo_38: ; preds = %_llgo_37, %_llgo_36 + %402 = call ptr @"github.com/goplus/llgo/internal/runtime.NewNamed"(i64 21, i64 8, i64 0, i64 0) + %403 = load ptr, ptr @_llgo_main.M, align 8 + %404 = icmp eq ptr %403, null + br i1 %404, label %_llgo_39, label %_llgo_40 + +_llgo_39: ; preds = %_llgo_38 + call void @"github.com/goplus/llgo/internal/runtime.SetDirectIface"(ptr %402) + store ptr %402, ptr @_llgo_main.M, align 8 + br label %_llgo_40 + +_llgo_40: ; preds = %_llgo_39, %_llgo_38 + %405 = load ptr, ptr @"map[_llgo_int]_llgo_string", align 8 + br i1 %404, label %_llgo_41, label %_llgo_42 + +_llgo_41: ; preds = %_llgo_40 + %406 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %407 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %406, i32 0, i32 0 + store ptr @4, ptr %407, align 8 + %408 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %406, i32 0, i32 1 + store i64 4, ptr %408, align 4 + %409 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %406, align 8 + %410 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %411 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %410, i32 0, i32 0 + store ptr @20, ptr %411, align 8 + %412 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %410, i32 0, i32 1 + store i64 1, ptr %412, align 4 + %413 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %410, align 8 + call void @"github.com/goplus/llgo/internal/runtime.InitNamed"(ptr %402, %"github.com/goplus/llgo/internal/runtime.String" %409, %"github.com/goplus/llgo/internal/runtime.String" %413, ptr %405, { ptr, i64, i64 } zeroinitializer, { ptr, i64, i64 } zeroinitializer) + br label %_llgo_42 + +_llgo_42: ; preds = %_llgo_41, %_llgo_40 ret void } diff --git a/cl/compile.go b/cl/compile.go index e36e838a..1f6478c1 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -195,6 +195,9 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun isInit := (f.Name() == "init" && sig.Recv() == nil) if isInit && state == pkgHasPatch { name = initFnNameOfHasPatch(name) + // TODO(xsw): pkg.init$guard has been set, change ssa.If to ssa.Jump + block := f.Blocks[0].Instrs[1].(*ssa.If).Block() + block.Succs[0], block.Succs[1] = block.Succs[1], block.Succs[0] } fn := pkg.FuncOf(name) @@ -298,7 +301,7 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, do if pyModInit = p.pyMod != ""; pyModInit { last = len(instrs) - 1 instrs = instrs[:last] - } else { + } else if p.state != pkgHasPatch { // TODO(xsw): confirm pyMod don't need to call AfterInit p.inits = append(p.inits, func() { pkg.AfterInit(b, ret) @@ -315,7 +318,7 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, do b.Call(pkg.FuncOf("main.init").Expr) } for i, instr := range instrs { - if i == 1 && doModInit && p.state == pkgInPatch { + if i == 1 && doModInit && p.state == pkgInPatch { // in patch package but no pkgFNoOldInit initFnNameOld := initFnNameOfHasPatch(p.fn.Name()) fnOld := pkg.NewFunc(initFnNameOld, llssa.NoArgsNoRet, llssa.InC) b.Call(fnOld.Expr) diff --git a/cl/import.go b/cl/import.go index d723f3a8..65e426f0 100644 --- a/cl/import.go +++ b/cl/import.go @@ -509,41 +509,6 @@ func (p *context) ensureLoaded(pkgTypes *types.Package) *types.Package { return pkgTypes } -func pkgKindByPath(pkgPath string) int { - switch pkgPath { - case "runtime/cgo", "unsafe": - return PkgDeclOnly - } - return PkgNormal -} - -func replaceGoName(v string, pos int) string { - switch v[:pos] { - case "runtime": - return "github.com/goplus/llgo/internal/runtime" + v[pos:] - } - return v -} - -func ignoreName(name string) bool { - /* TODO(xsw): confirm this is not needed more - if name == "unsafe.init" { - return true - } - */ - const internal = "internal/" - return (strings.HasPrefix(name, internal) && !supportedInternal(name[len(internal):])) || - strings.HasPrefix(name, "crypto/") || strings.HasPrefix(name, "runtime/") || - strings.HasPrefix(name, "arena.") || strings.HasPrefix(name, "maps.") || - strings.HasPrefix(name, "plugin.") -} - -func supportedInternal(name string) bool { - return strings.HasPrefix(name, "abi.") || strings.HasPrefix(name, "bytealg.") || - strings.HasPrefix(name, "oserror.") || strings.HasPrefix(name, "reflectlite.") || - strings.HasPrefix(name, "syscall/unix.") || strings.HasPrefix(name, "syscall/execenv.") -} - // ----------------------------------------------------------------------------- const ( @@ -609,3 +574,39 @@ func toBackground(bg string) llssa.Background { } // ----------------------------------------------------------------------------- + +func pkgKindByPath(pkgPath string) int { + switch pkgPath { + case "runtime/cgo", "unsafe": + return PkgDeclOnly + } + return PkgNormal +} + +func replaceGoName(v string, pos int) string { + switch v[:pos] { + case "runtime": + return "github.com/goplus/llgo/internal/runtime" + v[pos:] + } + return v +} + +func ignoreName(name string) bool { + /* TODO(xsw): confirm this is not needed more + if name == "unsafe.init" { + return true + } + */ + const internal = "internal/" + return (strings.HasPrefix(name, internal) && !supportedInternal(name[len(internal):])) || + strings.HasPrefix(name, "runtime/") || strings.HasPrefix(name, "arena.") || + strings.HasPrefix(name, "maps.") || strings.HasPrefix(name, "plugin.") +} + +func supportedInternal(name string) bool { + return strings.HasPrefix(name, "abi.") || strings.HasPrefix(name, "bytealg.") || + strings.HasPrefix(name, "itoa.") || strings.HasPrefix(name, "oserror.") || strings.HasPrefix(name, "reflectlite.") || + strings.HasPrefix(name, "syscall/unix.") || strings.HasPrefix(name, "syscall/execenv.") +} + +// ----------------------------------------------------------------------------- diff --git a/cmd/internal/version/version.go b/cmd/internal/version/version.go index f6fc6eb4..1a6adffc 100644 --- a/cmd/internal/version/version.go +++ b/cmd/internal/version/version.go @@ -21,7 +21,7 @@ import ( "runtime" "github.com/goplus/llgo/cmd/internal/base" - "github.com/goplus/llgo/xtool/env" + "github.com/goplus/llgo/x/env" ) // llgo version diff --git a/cpp/std/std_gc.go b/cpp/std/std_gc.go new file mode 100644 index 00000000..21902954 --- /dev/null +++ b/cpp/std/std_gc.go @@ -0,0 +1,39 @@ +//go:build !nogc +// +build !nogc + +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 std + +import ( + "unsafe" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/bdwgc" +) + +// ----------------------------------------------------------------------------- + +func allocString() *String { + ptr := bdwgc.Malloc(unsafe.Sizeof(String{})) + bdwgc.RegisterFinalizer(ptr, func(obj, data c.Pointer) { + (*String)(obj).Dispose() + }, nil, nil, nil) + return (*String)(ptr) +} + +// ----------------------------------------------------------------------------- diff --git a/cpp/std/std_nogc.go b/cpp/std/std_nogc.go new file mode 100644 index 00000000..20f8c880 --- /dev/null +++ b/cpp/std/std_nogc.go @@ -0,0 +1,35 @@ +//go:build nogc +// +build nogc + +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 std + +import ( + "unsafe" + + "github.com/goplus/llgo/c" +) + +// ----------------------------------------------------------------------------- + +func allocString() *String { + ptr := c.Malloc(unsafe.Sizeof(String{})) + return (*String)(ptr) +} + +// ----------------------------------------------------------------------------- diff --git a/cpp/std/string.go b/cpp/std/string.go index fa04524f..5427a51a 100644 --- a/cpp/std/string.go +++ b/cpp/std/string.go @@ -20,7 +20,6 @@ import ( "unsafe" "github.com/goplus/llgo/c" - "github.com/goplus/llgo/c/bdwgc" ) // ----------------------------------------------------------------------------- @@ -32,7 +31,7 @@ type StringView = string // String represents a C++ std::string object. type String struct { - Unused [24]byte + Unused [3]uintptr } // llgo:link (*String).InitEmpty C.stdStringInitEmpty @@ -52,14 +51,6 @@ func (s *String) Dispose() {} // ----------------------------------------------------------------------------- -func allocString() *String { - ptr := bdwgc.Malloc(unsafe.Sizeof(String{})) - bdwgc.RegisterFinalizer(ptr, func(obj, data c.Pointer) { - (*String)(obj).Dispose() - }, nil, nil, nil) - return (*String)(ptr) -} - // NewString creates a C++ std::string object. func NewString(v string) *String { ret := allocString() diff --git a/doc/How-to-support-a-C&C++-Library.md b/doc/How-to-support-a-C&C++-Library.md index 6c462b2d..14ac8bd6 100644 --- a/doc/How-to-support-a-C&C++-Library.md +++ b/doc/How-to-support-a-C&C++-Library.md @@ -1,8 +1,25 @@ -# How to support a C/C++ Library +How to support a C/C++ Library +===== +## Symbol Visibility +When llgo needs to link C or C++ libraries, symbol visibility is a crucial concept. It determines how C/C++ functions and methods are linked and utilized within llgo. Symbol visibility significantly impacts the handling of symbols in llgo bindings: visible symbols can typically be directly linked, while invisible symbols require wrapper functions. -# Support a C Library +The accessibility of symbols, particularly destructors, in dynamic libraries depends on their definition method. For instance, destructors that are explicitly declared in header files and implemented as non-inline functions in .cpp files typically appear in the dynamic library's symbol table, making them visible and directly linkable. -## Install a C Library +**Visible Symbols:** These are symbols that can be found in the dynamic library. They typically represent public API functions and methods. + +* Example: A class constructor explicitly declared in the header and implemented in a .cpp file. + +* Example: A non-inline destructor declared in the header and implemented in a .cpp file. + +**Invisible Symbols:** These are symbols that cannot be found in the dynamic library. This may include inline functions, templates, or certain constructors and destructors. + +* Example: A default constructor not explicitly declared. + +* Example: An inline destructor or a compiler-generated default destructor. + +## Support a C Library + +### Install a C Library We recommend using a package manager (such as brew, apt-get, winget, etc.) to install a C library. For example: @@ -10,11 +27,11 @@ We recommend using a package manager (such as brew, apt-get, winget, etc.) to in brew install inih ``` -## Writing Go Files to Link Library Functions +### Writing Go Files to Link Library Functions 1. On macOS, use `nm -gU libbar.dylib` to parse C-style symbols -```jsx +```bash 0000000000003e55 T _ini_parse ``` @@ -26,14 +43,14 @@ int ini_parse(const char* filename, ini_handler handler, void* user); 3. Create the corresponding Go file - ```c - inih/ - ├── _demo - ├── inih_demo - ├──inih_demo.go - └── inih.go +```bash +inih/ +├── _demo + ├── inih_demo + ├──inih_demo.go +└── inih.go +``` - ``` 4. In `inih.go`, use LLGoPackage to specify the location of the third-party library so that llgo can link to the third-party library. Both `pkg-config --libs inih` and `linih` are used to specify the location of the third-party library. ```go @@ -50,46 +67,46 @@ const ( 5. Write the corresponding function in `inih.go` - Note that the basic C function type mapping to Go function type can be found at [https://github.com/goplus/llgo/blob/main/doc/Type-Mapping-between-C-and-Go.md](https://github.com/goplus/llgo/blob/main/doc/Type-Mapping-between-C-and-Go.md). Some types requiring special handling are listed at the end of this document for reference. +Note that the basic C function type mapping to Go function type can be found at [https://github.com/goplus/llgo/blob/main/doc/Type-Mapping-between-C-and-Go.md](https://github.com/goplus/llgo/blob/main/doc/Type-Mapping-between-C-and-Go.md). Some types requiring special handling are listed at the end of this document for reference. - ```go - //go:linkname Parse C.ini_parse - func Parse(filename *c.Char, handler func(user c.Pointer, section *c.Char, name *c.Char, value *c.Char) c.Int, user c.Pointer) c.Int - ``` +```go +//go:linkname Parse C.ini_parse +func Parse(filename *c.Char, handler func(user c.Pointer, section *c.Char, name *c.Char, value *c.Char) c.Int, user c.Pointer) c.Int +``` 6. Write the function call in `inih_demo.go` - ```go - package main +```go +package main - import ( - "github.com/goplus/llgo/c" - "github.com/goplus/llgo/cpp/inih" - ) +import ( +"github.com/goplus/llgo/c" +"github.com/goplus/llgo/cpp/inih" +) - func main() { - filename := c.Str("path/to/yourIniFile") +func main() { +filename := c.Str("path/to/yourIniFile") - if inih.Parse(filename, func(user c.Pointer, section *c.Char, name *c.Char, value *c.Char) c.Int { - println("section:", c.GoString(section), "name:", c.GoString(name), "value:", c.GoString(value)) - return 1 - }, nil) < 0 { - println("Error parsing config file") - return - } - } - - ``` +if inih.Parse(filename, func(user c.Pointer, section *c.Char, name *c.Char, value *c.Char) c.Int { + println("section:", c.GoString(section), "name:", c.GoString(name), "value:", c.GoString(value)) + return 1 + }, nil) < 0 { + println("Error parsing config file") + return + } +} +``` + 7. Use llgo to run the demo - ```bash - cd inih/_demo/inih_demo - llgo run . - ``` +```bash +cd inih/_demo/inih_demo +llgo run . +``` -## Handling Special Types +### Handling Special Types -### Handling Enum Values in C +#### Handling Enum Values in C Use const to implement enum values @@ -106,19 +123,22 @@ typedef enum { BLEND_CUSTOM_SEPARATE // Blend textures using custom rgb/alpha separate src/dst factors (use rlSetBlendFactorsSeparate()) } BlendMode; */ + +type BlendMode c.Int + const ( - BLEND_ALPHA BlendMode = iota // Blend textures considering alpha (default) - BLEND_ADDITIVE // Blend textures adding colors - BLEND_MULTIPLIED // Blend textures multiplying colors - BLEND_ADD_COLORS // Blend textures adding colors (alternative) - BLEND_SUBTRACT_COLORS // Blend textures subtracting colors (alternative) - BLEND_ALPHA_PREMULTIPLY // Blend premultiplied textures considering alpha - BLEND_CUSTOM // Blend textures using custom src/dst factors (use rlSetBlendFactors()) - BLEND_CUSTOM_SEPARATE // Blend textures using custom rgb/alpha separate src/dst factors (use rlSetBlendFactorsSeparate()) + BLEND_ALPHA BlendMode = iota // Blend textures considering alpha (default) + BLEND_ADDITIVE // Blend textures adding colors + BLEND_MULTIPLIED // Blend textures multiplying colors + BLEND_ADD_COLORS // Blend textures adding colors (alternative) + BLEND_SUBTRACT_COLORS // Blend textures subtracting colors (alternative) + BLEND_ALPHA_PREMULTIPLY // Blend premultiplied textures considering alpha + BLEND_CUSTOM // Blend textures using custom src/dst factors (use rlSetBlendFactors()) + BLEND_CUSTOM_SEPARATE // Blend textures using custom rgb/alpha separate src/dst factors (use rlSetBlendFactorsSeparate()) ) ``` -### Handling Structs in C +#### Handling Structs in C ```go // If you need to use class member variables, like llgo/c/raylib @@ -133,14 +153,14 @@ typedef struct Vector4 { */ type Vector4 struct { - X float32 // Vector x component - Y float32 // Vector y component - Z float32 // Vector z component - W float32 // Vector w component + X float32 // Vector x component + Y float32 // Vector y component + Z float32 // Vector z component + W float32 // Vector w component } // If class member variables don't need to be exposed, like llgo/c/cjson, wrap functions that use these member variables as methods of the class. Example: -// + /* typedef struct cJSON { @@ -167,16 +187,15 @@ typedef struct cJSON // llgo:type C type JSON struct { - Unused [0]byte + Unused [0]byte } // llgo:link (*JSON).AddItem C.cJSON_AddItemToArray func (o *JSON) AddItem(item *JSON) c.Int { return 0 } - ``` For the size of Unused, if the methods bound to the structure do not need to create objects, i.e., the receiver of the Go methods bound to this structure is of pointer type, you can declare `Unused [0]byte`. Otherwise, you need to write a simple C file using the `sizeof` operator to calculate the size of the structure. Assuming the structure size is 38 bytes, then declare `Unused [38]byte`. -### Handling Function Pointers in C +#### Handling Function Pointers in C ```go // Convert function pointers to Go style and then declare function pointer types using aliases @@ -186,7 +205,7 @@ type Comp func(a c.Int) ``` -### Handling char ** Type in C +#### Handling char ** Type in C Handle char ** as `[]*c.Char` @@ -209,25 +228,24 @@ import ( ) func main() { - strings := make([]*c.Char, 4) - strings[0] = c.Str("hello") - strings[1] = c.Str("world") - strings[2] = c.Str("ni") - strings[3] = c.Str("hao") - ptrtest.PrintStrings(unsafe.SliceData(strings), c.Int(4)) + strings := make([]*c.Char, 4) + strings[0] = c.Str("hello") + strings[1] = c.Str("world") + strings[2] = c.Str("ni") + strings[3] = c.Str("hao") + ptrtest.PrintStrings(unsafe.SliceData(strings), c.Int(4)) } - ``` -# LLGO for C++ Third-Party Libraries +## LLGO for C++ Third-Party Libraries Using the C++ part of the inih library as an example -## Installation +### Installation Same as installing C libraries -## File Structure +### File Structure After migrating the C part of the inih library, just continue creating files in the same directory. @@ -241,12 +259,11 @@ inih/ ├── _wrap/cpp_wrap.cpp (optional) └── inih.go └── reader.go - ``` -## Writing Go Files to Link Library Functions +### Writing Go Files to Link Library Functions -### Migrating Ordinary Functions +#### Migrating Ordinary Functions Since the inih library does not have C++ style ordinary functions, we'll use an ordinary method of a class as an example. The specific process is the same. @@ -255,28 +272,24 @@ Ordinary functions can be directly linked using the corresponding symbol in the ```bash nm -gU $(brew --prefix inih)/lib/libINIReader.dylib > output.txt c++filt symbol.txt - ``` Function prototype ```cpp int ParseError() const; - ``` Example of `symbol.txt` ```bash 0000000000002992 T INIReader::ParseError() const - ``` Example of `output.txt` ```bash 0000000000002992 T __ZNK9INIReader10ParseErrorEv - ``` Find the offset of the function you want to use in `symbol.txt`, then go back to `output.txt` and find the symbol corresponding to that offset. @@ -287,10 +300,9 @@ For functions, generally use `go:linkname` to link. Here, refer to the migration // The inih library currently does not involve ordinary functions, this is for demonstration purposes only and is not needed for migrating inih //go:linkname ParseError C.__ZNK9INIReader10ParseErrorEv func ParseError() c.Int - ``` -### Migrating Classes +#### Migrating Classes - Use a struct to map the class. The writing method is the same as migrating a struct in the C library migration: @@ -299,51 +311,7 @@ func ParseError() c.Int type Reader struct { Unused [32]byte } - ``` -- Constructor - - - Constructor is explicitly declared in the class (can find the corresponding symbol in the dynamic library): - - Bind to the `InitFromBuffer` method of the struct and call it in the `NewReaderFile` function to initialize the class and return the class for Go to use. - - ```go - // NewReaderFile creates a new INIReader instance. - func NewReaderFile(fileName *std.String) (ret Reader) { - ret.InitFromFile(fileName) - return - } - /* - class INIReader - { - public: - explicit INIReader(const char *buffer, size_t buffer_size); - } - */ - - // llgo:link (*Reader).InitFromBuffer C._ZN9INIReaderC1EPKcm - func (r *Reader) InitFromBuffer(buffer *c.Char, bufferSize uintptr) {} - - ``` - - Constructor is not explicitly declared in the class (cannot find the corresponding symbol in the dynamic library) - - If the destructor is not explicitly declared in the source code, the compiler will automatically generate a default destructor. Use `extern "C"` to wrap it in cppWrap.cpp: - - ```jsx - extern "C" void INIReaderInit(INIReader* r) - { - r->INIReader(); - } - - ``` - - Link in Go: - - ```go - // llgo:link (*Reader).INIReaderInit C.INIReaderInit - func (r *Reader) INIReaderInit() {} - - ``` - Class Methods For general methods of the class, directly use `llgo:link` to link: @@ -353,30 +321,70 @@ func ParseError() c.Int func (r *Reader) GetInteger(section *std.String, name *std.String, defaultValue c.Long) c.Long { return 0 } - ``` +- Constructor + + - Explicitly Constructor: + + ```cpp + class INIReader { + public: + // Construct INIReader and parse given filename. + INI_API explicit INIReader(const std::string &filename); + } + ``` + Bind to the `InitFromFile` method of the struct and call it in the `NewReaderFile` function to initialize the class and return the class for Go to use. + + The following long string starting with `_ZN9INI` is the corresponding function prototype in the symbol table for `INIReader(const std::string &filename)` + ```go + // llgo:link (*Reader).InitFromFile C._ZN9INIReaderC1ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEE + func (r *Reader) InitFromFile(fileName *std.String) {} + + // NewReaderFile creates a new INIReader instance. + func NewReaderFile(fileName *std.String) (ret Reader) { + ret.InitFromFile(fileName) + return + } + ``` + - Implicitly Constructor + + In typical implementations of the inih library, directly invoking implicit constructors to instantiate reader objects is not recommended. For detailed examples of how bindings effectively handle non-exported symbols, please refer to the [Templates and Inlines](#templates-and-inlines) section. - Template or inline methods of the class will be introduced in the next section. - Destructor - Similar to the constructor process, after creating the class, use `defer` to call it explicitly: + Explicitly declared and non-inline destructors can be directly linked, consistent with the linking method of general class methods (see "Class Methods" section). For destructors that do not appear in the dynamic library's symbol table, a wrapper layer implementation is required. This wrapper in the C++ wrapper file (e.g., cppWrap.cpp) looks like: - ```go - reader := inih.NewReader(c.Str(buf), uintptr(len(buf))) - defer reader.Dispose() + ```cpp + extern "C" { + void INIReaderDispose(INIReader* r) { + r->~INIReader(); + } + } // extern "C" + ``` + This wrapper function explicitly calls the object's destructor. By using extern "C", we ensure that this function can be called by C code, allowing Go to link to it. + In the Go file: + ```go + // llgo:link (*Reader).Dispose C.INIReaderDispose + func (r *Reader) Dispose() {} + ``` + Here we link the Go Dispose method to the C++ wrapped INIReaderDispose function. + In actual usage: + We use defer to ensure that the Dispose method is called when the reader object goes out of scope, thus properly releasing resources. + ```go + reader := inih.NewReader(c.Str(buf), uintptr(len(buf))) + defer reader.Dispose() + ``` + This situation is analogous to the handling of inline functions and templates described in the following section. - ``` - -### Templates and Inlines +#### Templates and Inlines Templates or inlines do not generate symbols in dynamic libraries (dylib) (default constructors and destructors). To ensure that you can use C style symbols to link template or inline functions, create a C++ file and wrap it with `extern "C"`, then bind the functions directly in Go. -```c +```cpp // Using std::string as an example, not needed for migrating inih extern "C" void stdStringInitFromCStrLen(std::string* s, const char* cstr, size_t len) { new(s) std::string(cstr, len); } - ``` Then use LLGoFiles to link in Go: the writing of standard library's `LLGoFiles` and `LLGoPackage` is slightly different from third-party libraries. Using `std::string` and `spdlog` library as examples, inih does not involve this step: @@ -393,10 +401,9 @@ func (s *String) InitFromCStrLen(cstr *c.Char, n uintptr) {} const ( LLGoFiles = "$(pkg-config --cflags spdlog): cppWrap/cppWrap.cpp" LLGoPackage = "link: $(pkg-config --libs spdlog); -lspdlog -pthread -lfmt") - ``` -## Writing and Running the Demo +### Writing and Running the Demo ```go package main @@ -409,9 +416,8 @@ import ( func demoFromBuffer() { buf := `[settings] -username=admin -timeout=100 -` + username=admin + timeout=100` reader := inih.NewReader(c.Str(buf), uintptr(len(buf))) defer reader.Dispose() @@ -441,7 +447,6 @@ func main() { demoFromBuffer() demoFromFile() } - ``` -Use `llgo run reader_demo.go` to run in the directory where the demo is written. +Use `llgo run .` to run in the directory where the demo is written. diff --git a/doc/How-to-support-a-Rust-Library.md b/doc/How-to-support-a-Rust-Library.md new file mode 100644 index 00000000..7b7ba70d --- /dev/null +++ b/doc/How-to-support-a-Rust-Library.md @@ -0,0 +1,335 @@ +How to support a Rust Library +===== + +## Add Dependencies & Build Configuration + +Edit `Cargo.toml` to include necessary dependencies and configuration: + +```toml +[dependencies] +libc = "0.2" +csv = "1.1" + +[lib] +crate-type = ["cdylib"] # The generated dynamic library will conform to the C standard + +[build-dependencies] +cbindgen = "0.26.0" +``` + +## C-style wrapper for Rust + +### Import C Language Types + +Use types from the libc package for interoperability with C: + +```rust +use libc::{c_int, c_char, strlen}; +``` + +### Function Decoration and Attributes + +To ensure that Rust functions can be correctly called by C and LLGO, use the following decorators: + +- `#[no_mangle]` prevents the compiler from mangling the function name. +- `unsafe` is used to mark operations that are unsafe, especially when dealing with raw pointers. +- `extern "C"` specifies the use of C calling conventions. + +```rust +pub fn add_numbers(a: i32, b: i32) -> i32 { + a + b +} +``` + +After packaging: + +```rust +#[no_mangle] +pub unsafe extern "C" fn add_numbers_c(a: i32, b: i32) -> i32 { + add_numbers(a, b) +} +``` + +### Memory Management + +Use `Box` to manage dynamic memory to ensure correct memory release between Rust and C: + +```rust +let config = Config::new(); +``` + +After packaging: + +```rust +pub unsafe extern "C" fn sled_create_config() -> \*mut Config { + Box::into_raw(Box::new(Config::new())) +} + +#[no_mangle] +pub unsafe extern "C" fn sled_free_config(config: \*mut Config) { + drop(Box::from_raw(config)); +} +``` + +### Handling Generic Pointers + +Address the interfacing issues between generic pointers in C and Rust: + +```rust +let mut reader = ReaderBuilder::new().from_path(file_path)?; +``` + +After packaging: + +```rust +// Create a new CSV reader for the specified file path. +#[no_mangle] +pub extern "C" fn csv_reader_new(file_path: *const c_char) -> *mut c_void { + let file_path = unsafe { + if file_path.is_null() { return ptr::null_mut(); } + match CStr::from_ptr(file_path).to_str() { + Ok(s) => s, + Err(_) => return ptr::null_mut(), + } + }; + + let reader = csv::ReaderBuilder::new().from_path(file_path); + match reader { + Ok(r) => Box::into_raw(Box::new(r)) as *mut c_void, + Err(_) => ptr::null_mut(), + } +} + +// Free the memory allocated for the CSV reader. +#[no_mangle] +pub extern "C" fn csv_reader_free(ptr: *mut c_void) { + if !ptr.is_null() { + let reader: Box> = unsafe { Box::from_raw(ptr as *mut csv::Reader) }; + std::mem::drop(reader); + } +} +``` + +### String Handling + +Convert strings between C and Rust: + +```rust +let mut record = csv::StringRecord::new(); + +while reader.read_record(&mut record)? { + // Print each record + println!("{:?}", record); +} +``` + +After packaging: + +```rust +// Read the next record from the CSV reader and return it as a C string. +#[no_mangle] +pub extern "C" fn csv_reader_read_record(ptr: *mut c_void) -> *const c_char { + let reader = unsafe { + assert!(!ptr.is_null()); + &mut *(ptr as *mut csv::Reader) + }; + + let mut record = csv::StringRecord::new(); + match reader.read_record(&mut record) { + Ok(true) => match CString::new(format!("{:?}\n", record)) { + Ok(c_string) => c_string.into_raw(), + Err(_) => ptr::null(), + }, + _ => ptr::null(), + } +} + +// Free the memory allocated for a C string returned by other functions. +#[no_mangle] +pub extern "C" fn free_string(s: *mut c_char) { + if s.is_null() { + return; + } + unsafe { + let c_string = CString::from_raw(s); + std::mem::drop(c_string); + } +} +``` + +## Generate Header File + +Edit `cbindgen.toml` to configure the header file generation rules: + +```toml +# See https://github.com/mozilla/cbindgen/blob/master/docs.md#cbindgentoml for +# a list of possible configuration values. +language = "C" +``` + +Use cbindgen to generate a C header file, automating this process through a `build.rs` script: + +```rust +fn main() { + let config = cbindgen::Config::from_file("cbindgen.toml").expect("Config file not found."); + cbindgen::generate_with_config(&crate_dir, config).unwrap().write_to_file("target/include/csv_wrapper.h"); +} +``` + +## Compilation and Installation + +### Build the dynamic library: + +```sh +cargo build --release +``` + +### Install dylib-installer + +Install the [dylib-installer](https://github.com/hackerchai/dylib-installer) tool, which is used to install dynamic libraries: + +```sh +brew tap hackerchai/tap +brew install dylib-installer +``` + +Or you can install it using Cargo: + +```sh +cargo install dylib_installer +``` + +### Install Dynamic Library + +Use dylib-installer to install the built dynamic library and the header file into the system directory: + +```sh +sudo dylib_installer +``` + +### Check the Installation + +You can check the installation by running the following command: + +```sh +pkg-config --libs --cflags +``` + +If everything is installed correctly, you will see the output like this (depending on your system): + +```sh +-I/usr/local/include -L/usr/local/lib -l +``` + +## LLGO Mapping + +Map functions from the Rust library to an LLGO package, ensuring type consistency: + +- LLGoPackage + +Specify `LLGoPackage` and use `pkg-config` to find the location of the lib library. + +```go +const ( + LLGoPackage = "link: $(pkg-config --libs csv_wrapper); -lcsv_wrapper" +) +``` + +- Type + +If you want to use variables inside the struct, you can add them accordingly. +If it can't be represented directly or is not needed, it can be represented in the form `Unused []byte`, the length of the array is determined by its size, and if the struct is only used as a pointer, then the array length can be `0`. + +```go +type Reader struct { + Unused [0]byte +} + +// type Reader struct { +// Unused [8]byte +// } +``` + +If we want to calculate the size of this structure, we can use the following C code: + +```c +printf("%d\n", sizeof(csv_reader)); +``` + +- Ordinary functions + +Ordinary functions can be mapped in the form of `//go:linkname`. + +```c +csv_reader *csv_reader_new(const char *file_path); +``` + +After mapping: + +```go +//go:linkname NewReader C.csv_reader_new +func NewReader(file_path *c.Char) *Reader +``` + +- Method + +Methods need to be mapped in the form of `// llgo:link (*Receiver)`. + +```c +void csv_reader_free(csv_reader *reader); + +const char *csv_reader_read_record(csv_reader *reader); +``` + +After mapping: + +We can extract the first parameter as Receiver: + +```go +// llgo:link (*Reader).Free C.csv_reader_free +func (reader *Reader) Free() {} + +// llgo:link (*Reader).ReadRecord C.csv_reader_read_record +func (reader *Reader) ReadRecord() *c.Char { return nil } +``` + +- Function pointer + +If you use a function pointer, that is, declare the function as a type separately, you need to use `// llgo:type C` to declare it. + +```c +typedef size_t (*hyper_io_read_callback)(void*, struct hyper_context*, uint8_t*, size_t); + +void hyper_io_set_read(struct hyper_io *io, hyper_io_read_callback func); +``` + +After mapping: + +```go +// llgo:type C +type IoReadCallback func(c.Pointer, *Context, *uint8, uintptr) uintptr + +// llgo:link (*Io).SetRead C.hyper_io_set_read +func (io *Io) SetRead(callback IoReadCallback) {} +``` + +Or declare the function directly in the parameter. + +```go +// llgo:link (*Io).SetRead C.hyper_io_set_read +func (io *Io) SetRead(ioSetReadCb func(c.Pointer, *Context, *uint8, uintptr) uintptr) {} +``` + +## Writing Examples and README + +Finally, provide example code and a detailed README file to help users understand how to use the generated library. + +## Example Code + +You can find the migrated examples in the [llgoexamples](https://github.com/goplus/llgoexamples). The migrated Rust libraries are in the `lib` directory, and the migrated mapping files and Go demos are in the `rust` directory. + +Such as: + +- CSV: [csv.rs](https://github.com/goplus/llgoexamples/blob/main/lib/rust/csv-wrapper/src/lib.rs) --> [csv.go](https://github.com/goplus/llgoexamples/blob/main/rust/csv/csv.go) +- Sled: [sled.rs](https://github.com/goplus/llgoexamples/blob/main/lib/rust/sled/src/lib.rs) --> [sled.go](https://github.com/goplus/llgoexamples/blob/main/rust/sled/sled.go) +- ... diff --git a/internal/build/_overlay/go/parser/resolver.go b/internal/build/_overlay/go/parser/resolver.go new file mode 100644 index 00000000..a7338135 --- /dev/null +++ b/internal/build/_overlay/go/parser/resolver.go @@ -0,0 +1,612 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package parser + +import ( + "fmt" + "go/ast" + "go/token" + "strings" +) + +const debugResolve = false + +// resolveFile walks the given file to resolve identifiers within the file +// scope, updating ast.Ident.Obj fields with declaration information. +// +// If declErr is non-nil, it is used to report declaration errors during +// resolution. tok is used to format position in error messages. +func resolveFile(file *ast.File, handle *token.File, declErr func(token.Pos, string)) { + pkgScope := ast.NewScope(nil) + r := &resolver{ + handle: handle, + declErr: declErr, + topScope: pkgScope, + pkgScope: pkgScope, + depth: 1, + } + + for _, decl := range file.Decls { + ast.Walk(r, decl) + } + + r.closeScope() + assert(r.topScope == nil, "unbalanced scopes") + assert(r.labelScope == nil, "unbalanced label scopes") + + // resolve global identifiers within the same file + i := 0 + for _, ident := range r.unresolved { + // i <= index for current ident + assert(ident.Obj == unresolved, "object already resolved") + ident.Obj = r.pkgScope.Lookup(ident.Name) // also removes unresolved sentinel + if ident.Obj == nil { + r.unresolved[i] = ident + i++ + } else if debugResolve { + pos := ident.Obj.Decl.(interface{ Pos() token.Pos }).Pos() + r.trace("resolved %s@%v to package object %v", ident.Name, ident.Pos(), pos) + } + } + file.Scope = r.pkgScope + file.Unresolved = r.unresolved[0:i] +} + +const maxScopeDepth int = 1e3 + +type resolver struct { + handle *token.File + declErr func(token.Pos, string) + + // Ordinary identifier scopes + pkgScope *ast.Scope // pkgScope.Outer == nil + topScope *ast.Scope // top-most scope; may be pkgScope + unresolved []*ast.Ident // unresolved identifiers + depth int // scope depth + + // Label scopes + // (maintained by open/close LabelScope) + labelScope *ast.Scope // label scope for current function + targetStack [][]*ast.Ident // stack of unresolved labels +} + +func (r *resolver) trace(format string, args ...any) { + fmt.Println(strings.Repeat(". ", r.depth) + r.sprintf(format, args...)) +} + +func (r *resolver) sprintf(format string, args ...any) string { + for i, arg := range args { + switch arg := arg.(type) { + case token.Pos: + args[i] = r.handle.Position(arg) + } + } + return fmt.Sprintf(format, args...) +} + +func (r *resolver) openScope(pos token.Pos) { + r.depth++ + if r.depth > maxScopeDepth { + panic(bailout{pos: pos, msg: "exceeded max scope depth during object resolution"}) + } + if debugResolve { + r.trace("opening scope @%v", pos) + } + r.topScope = ast.NewScope(r.topScope) +} + +func (r *resolver) closeScope() { + r.depth-- + if debugResolve { + r.trace("closing scope") + } + r.topScope = r.topScope.Outer +} + +func (r *resolver) openLabelScope() { + r.labelScope = ast.NewScope(r.labelScope) + r.targetStack = append(r.targetStack, nil) +} + +func (r *resolver) closeLabelScope() { + // resolve labels + n := len(r.targetStack) - 1 + scope := r.labelScope + for _, ident := range r.targetStack[n] { + ident.Obj = scope.Lookup(ident.Name) + if ident.Obj == nil && r.declErr != nil { + r.declErr(ident.Pos(), fmt.Sprintf("label %s undefined", ident.Name)) + } + } + // pop label scope + r.targetStack = r.targetStack[0:n] + r.labelScope = r.labelScope.Outer +} + +func (r *resolver) declare(decl, data any, scope *ast.Scope, kind ast.ObjKind, idents ...*ast.Ident) { + for _, ident := range idents { + if ident.Obj != nil { + panic(fmt.Sprintf("%v: identifier %s already declared or resolved", ident.Pos(), ident.Name)) + } + obj := ast.NewObj(kind, ident.Name) + // remember the corresponding declaration for redeclaration + // errors and global variable resolution/typechecking phase + obj.Decl = decl + obj.Data = data + // Identifiers (for receiver type parameters) are written to the scope, but + // never set as the resolved object. See issue #50956. + if _, ok := decl.(*ast.Ident); !ok { + ident.Obj = obj + } + if ident.Name != "_" { + if debugResolve { + r.trace("declaring %s@%v", ident.Name, ident.Pos()) + } + if alt := scope.Insert(obj); alt != nil && r.declErr != nil { + prevDecl := "" + if pos := alt.Pos(); pos.IsValid() { + prevDecl = r.sprintf("\n\tprevious declaration at %v", pos) + } + r.declErr(ident.Pos(), fmt.Sprintf("%s redeclared in this block%s", ident.Name, prevDecl)) + } + } + } +} + +func (r *resolver) shortVarDecl(decl *ast.AssignStmt) { + // Go spec: A short variable declaration may redeclare variables + // provided they were originally declared in the same block with + // the same type, and at least one of the non-blank variables is new. + n := 0 // number of new variables + for _, x := range decl.Lhs { + if ident, isIdent := x.(*ast.Ident); isIdent { + assert(ident.Obj == nil, "identifier already declared or resolved") + obj := ast.NewObj(ast.Var, ident.Name) + // remember corresponding assignment for other tools + obj.Decl = decl + ident.Obj = obj + if ident.Name != "_" { + if debugResolve { + r.trace("declaring %s@%v", ident.Name, ident.Pos()) + } + if alt := r.topScope.Insert(obj); alt != nil { + ident.Obj = alt // redeclaration + } else { + n++ // new declaration + } + } + } + } + if n == 0 && r.declErr != nil { + r.declErr(decl.Lhs[0].Pos(), "no new variables on left side of :=") + } +} + +// The unresolved object is a sentinel to mark identifiers that have been added +// to the list of unresolved identifiers. The sentinel is only used for verifying +// internal consistency. +var unresolved = new(ast.Object) + +// If x is an identifier, resolve attempts to resolve x by looking up +// the object it denotes. If no object is found and collectUnresolved is +// set, x is marked as unresolved and collected in the list of unresolved +// identifiers. +func (r *resolver) resolve(ident *ast.Ident, collectUnresolved bool) { + if ident.Obj != nil { + panic(r.sprintf("%v: identifier %s already declared or resolved", ident.Pos(), ident.Name)) + } + // '_' should never refer to existing declarations, because it has special + // handling in the spec. + if ident.Name == "_" { + return + } + for s := r.topScope; s != nil; s = s.Outer { + if obj := s.Lookup(ident.Name); obj != nil { + if debugResolve { + r.trace("resolved %v:%s to %v", ident.Pos(), ident.Name, obj) + } + assert(obj.Name != "", "obj with no name") + // Identifiers (for receiver type parameters) are written to the scope, + // but never set as the resolved object. See issue #50956. + if _, ok := obj.Decl.(*ast.Ident); !ok { + ident.Obj = obj + } + return + } + } + // all local scopes are known, so any unresolved identifier + // must be found either in the file scope, package scope + // (perhaps in another file), or universe scope --- collect + // them so that they can be resolved later + if collectUnresolved { + ident.Obj = unresolved + r.unresolved = append(r.unresolved, ident) + } +} + +func (r *resolver) walkExprs(list []ast.Expr) { + for _, node := range list { + ast.Walk(r, node) + } +} + +func (r *resolver) walkLHS(list []ast.Expr) { + for _, expr := range list { + expr := unparen(expr) + if _, ok := expr.(*ast.Ident); !ok && expr != nil { + ast.Walk(r, expr) + } + } +} + +func (r *resolver) walkStmts(list []ast.Stmt) { + for _, stmt := range list { + ast.Walk(r, stmt) + } +} + +func (r *resolver) Visit(node ast.Node) ast.Visitor { + if debugResolve && node != nil { + r.trace("node %T@%v", node, node.Pos()) + } + + switch n := node.(type) { + + // Expressions. + case *ast.Ident: + r.resolve(n, true) + + case *ast.FuncLit: + r.openScope(n.Pos()) + defer r.closeScope() + r.walkFuncType(n.Type) + r.walkBody(n.Body) + + case *ast.SelectorExpr: + ast.Walk(r, n.X) + // Note: don't try to resolve n.Sel, as we don't support qualified + // resolution. + + case *ast.StructType: + r.openScope(n.Pos()) + defer r.closeScope() + r.walkFieldList(n.Fields, ast.Var) + + case *ast.FuncType: + r.openScope(n.Pos()) + defer r.closeScope() + r.walkFuncType(n) + + case *ast.CompositeLit: + if n.Type != nil { + ast.Walk(r, n.Type) + } + for _, e := range n.Elts { + if kv, _ := e.(*ast.KeyValueExpr); kv != nil { + // See issue #45160: try to resolve composite lit keys, but don't + // collect them as unresolved if resolution failed. This replicates + // existing behavior when resolving during parsing. + if ident, _ := kv.Key.(*ast.Ident); ident != nil { + r.resolve(ident, false) + } else { + ast.Walk(r, kv.Key) + } + ast.Walk(r, kv.Value) + } else { + ast.Walk(r, e) + } + } + + case *ast.InterfaceType: + r.openScope(n.Pos()) + defer r.closeScope() + r.walkFieldList(n.Methods, ast.Fun) + + // Statements + case *ast.LabeledStmt: + r.declare(n, nil, r.labelScope, ast.Lbl, n.Label) + ast.Walk(r, n.Stmt) + + case *ast.AssignStmt: + r.walkExprs(n.Rhs) + if n.Tok == token.DEFINE { + r.shortVarDecl(n) + } else { + r.walkExprs(n.Lhs) + } + + case *ast.BranchStmt: + // add to list of unresolved targets + if n.Tok != token.FALLTHROUGH && n.Label != nil { + depth := len(r.targetStack) - 1 + r.targetStack[depth] = append(r.targetStack[depth], n.Label) + } + + case *ast.BlockStmt: + r.openScope(n.Pos()) + defer r.closeScope() + r.walkStmts(n.List) + + case *ast.IfStmt: + r.openScope(n.Pos()) + defer r.closeScope() + if n.Init != nil { + ast.Walk(r, n.Init) + } + ast.Walk(r, n.Cond) + ast.Walk(r, n.Body) + if n.Else != nil { + ast.Walk(r, n.Else) + } + + case *ast.CaseClause: + r.walkExprs(n.List) + r.openScope(n.Pos()) + defer r.closeScope() + r.walkStmts(n.Body) + + case *ast.SwitchStmt: + r.openScope(n.Pos()) + defer r.closeScope() + if n.Init != nil { + ast.Walk(r, n.Init) + } + if n.Tag != nil { + // The scope below reproduces some unnecessary behavior of the parser, + // opening an extra scope in case this is a type switch. It's not needed + // for expression switches. + // TODO: remove this once we've matched the parser resolution exactly. + if n.Init != nil { + r.openScope(n.Tag.Pos()) + defer r.closeScope() + } + ast.Walk(r, n.Tag) + } + if n.Body != nil { + r.walkStmts(n.Body.List) + } + + case *ast.TypeSwitchStmt: + if n.Init != nil { + r.openScope(n.Pos()) + defer r.closeScope() + ast.Walk(r, n.Init) + } + r.openScope(n.Assign.Pos()) + defer r.closeScope() + ast.Walk(r, n.Assign) + // s.Body consists only of case clauses, so does not get its own + // scope. + if n.Body != nil { + r.walkStmts(n.Body.List) + } + + case *ast.CommClause: + r.openScope(n.Pos()) + defer r.closeScope() + if n.Comm != nil { + ast.Walk(r, n.Comm) + } + r.walkStmts(n.Body) + + case *ast.SelectStmt: + // as for switch statements, select statement bodies don't get their own + // scope. + if n.Body != nil { + r.walkStmts(n.Body.List) + } + + case *ast.ForStmt: + r.openScope(n.Pos()) + defer r.closeScope() + if n.Init != nil { + ast.Walk(r, n.Init) + } + if n.Cond != nil { + ast.Walk(r, n.Cond) + } + if n.Post != nil { + ast.Walk(r, n.Post) + } + ast.Walk(r, n.Body) + + case *ast.RangeStmt: + r.openScope(n.Pos()) + defer r.closeScope() + ast.Walk(r, n.X) + var lhs []ast.Expr + if n.Key != nil { + lhs = append(lhs, n.Key) + } + if n.Value != nil { + lhs = append(lhs, n.Value) + } + if len(lhs) > 0 { + if n.Tok == token.DEFINE { + // Note: we can't exactly match the behavior of object resolution + // during the parsing pass here, as it uses the position of the RANGE + // token for the RHS OpPos. That information is not contained within + // the AST. + as := &ast.AssignStmt{ + Lhs: lhs, + Tok: token.DEFINE, + TokPos: n.TokPos, + Rhs: []ast.Expr{&ast.UnaryExpr{Op: token.RANGE, X: n.X}}, + } + // TODO(rFindley): this walkLHS reproduced the parser resolution, but + // is it necessary? By comparison, for a normal AssignStmt we don't + // walk the LHS in case there is an invalid identifier list. + r.walkLHS(lhs) + r.shortVarDecl(as) + } else { + r.walkExprs(lhs) + } + } + ast.Walk(r, n.Body) + + // Declarations + case *ast.GenDecl: + switch n.Tok { + case token.CONST, token.VAR: + for i, spec := range n.Specs { + spec := spec.(*ast.ValueSpec) + kind := ast.Con + if n.Tok == token.VAR { + kind = ast.Var + } + r.walkExprs(spec.Values) + if spec.Type != nil { + ast.Walk(r, spec.Type) + } + r.declare(spec, i, r.topScope, kind, spec.Names...) + } + case token.TYPE: + for _, spec := range n.Specs { + spec := spec.(*ast.TypeSpec) + // Go spec: The scope of a type identifier declared inside a function begins + // at the identifier in the TypeSpec and ends at the end of the innermost + // containing block. + r.declare(spec, nil, r.topScope, ast.Typ, spec.Name) + if spec.TypeParams != nil { + r.openScope(spec.Pos()) + r.walkTParams(spec.TypeParams) + r.closeScope() + } + ast.Walk(r, spec.Type) + } + } + + case *ast.FuncDecl: + // Open the function scope. + r.openScope(n.Pos()) + defer r.closeScope() + + r.walkRecv(n.Recv) + + // Type parameters are walked normally: they can reference each other, and + // can be referenced by normal parameters. + if n.Type.TypeParams != nil { + r.walkTParams(n.Type.TypeParams) + // TODO(rFindley): need to address receiver type parameters. + } + + // Resolve and declare parameters in a specific order to get duplicate + // declaration errors in the correct location. + r.resolveList(n.Type.Params) + r.resolveList(n.Type.Results) + r.declareList(n.Recv, ast.Var) + r.declareList(n.Type.Params, ast.Var) + r.declareList(n.Type.Results, ast.Var) + + r.walkBody(n.Body) + if n.Recv == nil && n.Name.Name != "init" { + r.declare(n, nil, r.pkgScope, ast.Fun, n.Name) + } + + default: + return r + } + + return nil +} + +func (r *resolver) walkFuncType(typ *ast.FuncType) { + // typ.TypeParams must be walked separately for FuncDecls. + r.resolveList(typ.Params) + r.resolveList(typ.Results) + r.declareList(typ.Params, ast.Var) + r.declareList(typ.Results, ast.Var) +} + +func (r *resolver) resolveList(list *ast.FieldList) { + if list == nil { + return + } + for _, f := range list.List { + if f.Type != nil { + ast.Walk(r, f.Type) + } + } +} + +func (r *resolver) declareList(list *ast.FieldList, kind ast.ObjKind) { + if list == nil { + return + } + for _, f := range list.List { + r.declare(f, nil, r.topScope, kind, f.Names...) + } +} + +func (r *resolver) walkRecv(recv *ast.FieldList) { + // If our receiver has receiver type parameters, we must declare them before + // trying to resolve the rest of the receiver, and avoid re-resolving the + // type parameter identifiers. + if recv == nil || len(recv.List) == 0 { + return // nothing to do + } + typ := recv.List[0].Type + if ptr, ok := typ.(*ast.StarExpr); ok { + typ = ptr.X + } + + var declareExprs []ast.Expr // exprs to declare + var resolveExprs []ast.Expr // exprs to resolve + switch typ := typ.(type) { + case *ast.IndexExpr: + declareExprs = []ast.Expr{typ.Index} + resolveExprs = append(resolveExprs, typ.X) + case *ast.IndexListExpr: + declareExprs = typ.Indices + resolveExprs = append(resolveExprs, typ.X) + default: + resolveExprs = append(resolveExprs, typ) + } + for _, expr := range declareExprs { + if id, _ := expr.(*ast.Ident); id != nil { + r.declare(expr, nil, r.topScope, ast.Typ, id) + } else { + // The receiver type parameter expression is invalid, but try to resolve + // it anyway for consistency. + resolveExprs = append(resolveExprs, expr) + } + } + for _, expr := range resolveExprs { + if expr != nil { + ast.Walk(r, expr) + } + } + // The receiver is invalid, but try to resolve it anyway for consistency. + for _, f := range recv.List[1:] { + if f.Type != nil { + ast.Walk(r, f.Type) + } + } +} + +func (r *resolver) walkFieldList(list *ast.FieldList, kind ast.ObjKind) { + if list == nil { + return + } + r.resolveList(list) + r.declareList(list, kind) +} + +// walkTParams is like walkFieldList, but declares type parameters eagerly so +// that they may be resolved in the constraint expressions held in the field +// Type. +func (r *resolver) walkTParams(list *ast.FieldList) { + r.declareList(list, ast.Typ) + r.resolveList(list) +} + +func (r *resolver) walkBody(body *ast.BlockStmt) { + if body == nil { + return + } + r.openLabelScope() + defer r.closeLabelScope() + r.walkStmts(body.List) +} diff --git a/internal/build/build.go b/internal/build/build.go index 0d2165e5..668a307c 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -20,6 +20,7 @@ import ( "debug/macho" "fmt" "go/ast" + "go/build" "go/constant" "go/token" "go/types" @@ -135,7 +136,12 @@ func Do(args []string, conf *Config) { llssa.Initialize(llssa.InitAll) - prog := llssa.NewProgram(nil) + target := &llssa.Target{ + GOOS: build.Default.GOOS, + GOARCH: build.Default.GOARCH, + } + + prog := llssa.NewProgram(target) sizes := prog.TypeSizes dedup := packages.NewDeduper() dedup.SetPreload(func(pkg *types.Package, files []*ast.File) { @@ -283,7 +289,7 @@ func buildAllPkgs(ctx *context, initial []*packages.Package, verbose bool) (pkgs for _, param := range altParts { param = strings.TrimSpace(param) if strings.ContainsRune(param, '$') { - expd = strings.TrimSpace(env.ExpandEnv(param)) + expd = env.ExpandEnv(param) ctx.nLibdir++ } else { expd = param @@ -355,7 +361,11 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, llFiles args, "-rpath", "$ORIGIN", "-rpath", "$ORIGIN/../lib", + "-fdata-sections", + "-ffunction-sections", "-Xlinker", "--gc-sections", + "-lm", + "-latomic", "-lpthread", // libpthread is built-in since glibc 2.34 (2021-08-01); we need to support earlier versions. ) } @@ -747,15 +757,24 @@ func findDylibDep(exe, lib string) string { type none struct{} var hasAltPkg = map[string]none{ + "crypto/md5": {}, + "crypto/sha1": {}, + "crypto/sha256": {}, + "crypto/sha512": {}, + "crypto/rand": {}, "fmt": {}, + "hash/crc32": {}, "internal/abi": {}, "internal/bytealg": {}, + "internal/itoa": {}, "internal/oserror": {}, "internal/reflectlite": {}, "internal/syscall/execenv": {}, "internal/syscall/unix": {}, "math": {}, + "math/big": {}, "math/cmplx": {}, + "math/rand": {}, "reflect": {}, "sync": {}, "sync/atomic": {}, @@ -764,10 +783,7 @@ var hasAltPkg = map[string]none{ "os": {}, "os/exec": {}, "runtime": {}, -} - -var overlayFiles = map[string]string{ - "math/exp_amd64.go": "package math;", + "io": {}, } func check(err error) { diff --git a/internal/build/overlay.go b/internal/build/overlay.go new file mode 100644 index 00000000..90724cc0 --- /dev/null +++ b/internal/build/overlay.go @@ -0,0 +1,13 @@ +package build + +import ( + _ "embed" +) + +//go:embed _overlay/go/parser/resolver.go +var go_parser_resolver string + +var overlayFiles = map[string]string{ + "math/exp_amd64.go": "package math;", + "go/parser/resolver.go": go_parser_resolver, +} diff --git a/internal/lib/crypto/md5/md5.go b/internal/lib/crypto/md5/md5.go new file mode 100644 index 00000000..7865000e --- /dev/null +++ b/internal/lib/crypto/md5/md5.go @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 md5 + +// llgo:skipall +import ( + "crypto" + "hash" + "unsafe" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/openssl" +) + +func init() { + crypto.RegisterHash(crypto.MD5, New) +} + +// The blocksize of MD5 in bytes. +const BlockSize = 64 + +// The size of an MD5 checksum in bytes. +const Size = 16 + +type digest struct { + ctx openssl.MD5_CTX +} + +func (d *digest) Size() int { return Size } + +func (d *digest) BlockSize() int { return BlockSize } + +func (d *digest) Reset() { + d.ctx.Init() +} + +func (d *digest) Write(p []byte) (nn int, err error) { + d.ctx.UpdateBytes(p) + return len(p), nil +} + +func (d *digest) Sum(in []byte) []byte { + hash := (*[Size]byte)(c.Alloca(Size)) + d.ctx.Final((*byte)(unsafe.Pointer(hash))) + return append(in, hash[:]...) +} + +func New() hash.Hash { + d := new(digest) + d.ctx.Init() + return d +} + +func Sum(data []byte) (ret [Size]byte) { + openssl.MD5Bytes(data, &ret[0]) + return +} diff --git a/internal/lib/crypto/rand/rand.go b/internal/lib/crypto/rand/rand.go new file mode 100644 index 00000000..091614ca --- /dev/null +++ b/internal/lib/crypto/rand/rand.go @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 rand + +// llgo:skipall +import ( + "io" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/openssl" + "github.com/qiniu/x/errors" +) + +type rndReader struct{} + +func (rndReader) Read(p []byte) (n int, err error) { + return Read(p) +} + +// Reader is a global, shared instance of a cryptographically +// secure random number generator. +var Reader io.Reader = rndReader{} + +type opensslError struct { + file *c.Char + line c.Int + flags c.Int + function *c.Char + data *c.Char + err openssl.Errno +} + +// [error code]:[library name]:[function name]:[reason string]:[[filename:line]]: [text message] +func (p *opensslError) Error() string { + const bufsize = 1024 + buf := (*c.Char)(c.Alloca(bufsize)) + lib := openssl.ERRLibErrorString(p.err) + reason := openssl.ERRReasonErrorString(p.err) + n := uintptr(c.Snprintf( + buf, bufsize, + c.Str("%d:%s:%s:%s:[%s:%d]: "), + p.err, lib, p.function, reason, p.file, p.line)) + n += c.Strlen(openssl.ERRErrorString(p.err, c.Advance(buf, n))) + return c.GoString(buf, n) +} + +func getError() *opensslError { + ret := new(opensslError) + err := openssl.ERRGetErrorAll(&ret.file, &ret.line, &ret.function, &ret.data, &ret.flags) + if err == 0 { + return nil + } + return ret +} + +func getErrors() error { + var errs errors.List + for openssl.ERRPeekError() != 0 { + errs.Add(getError()) + } + return errs.ToError() +} + +// Read is a helper function that calls Reader.Read using io.ReadFull. +// On return, n == len(b) if and only if err == nil. +func Read(b []byte) (n int, err error) { + if openssl.RANDBytes(b) != 0 { + return len(b), nil + } + return 0, getErrors() +} + +/* TODO(xsw): +// batched returns a function that calls f to populate a []byte by chunking it +// into subslices of, at most, readMax bytes. +func batched(f func([]byte) error, readMax int) func([]byte) error { + return func(out []byte) error { + for len(out) > 0 { + read := len(out) + if read > readMax { + read = readMax + } + if err := f(out[:read]); err != nil { + return err + } + out = out[read:] + } + return nil + } +} +*/ diff --git a/internal/lib/crypto/rand/util.go b/internal/lib/crypto/rand/util.go new file mode 100644 index 00000000..51e10c8f --- /dev/null +++ b/internal/lib/crypto/rand/util.go @@ -0,0 +1,98 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rand + +/* TODO(xsw): +import ( + "errors" + "io" + "math/big" +) + +// Prime returns a number of the given bit length that is prime with high probability. +// Prime will return error for any error returned by rand.Read or if bits < 2. +func Prime(rand io.Reader, bits int) (*big.Int, error) { + if bits < 2 { + return nil, errors.New("crypto/rand: prime size must be at least 2-bit") + } + + b := uint(bits % 8) + if b == 0 { + b = 8 + } + + bytes := make([]byte, (bits+7)/8) + p := new(big.Int) + + for { + if _, err := io.ReadFull(rand, bytes); err != nil { + return nil, err + } + + // Clear bits in the first byte to make sure the candidate has a size <= bits. + bytes[0] &= uint8(int(1<= 2 { + bytes[0] |= 3 << (b - 2) + } else { + // Here b==1, because b cannot be zero. + bytes[0] |= 1 + if len(bytes) > 1 { + bytes[1] |= 0x80 + } + } + // Make the value odd since an even number this large certainly isn't prime. + bytes[len(bytes)-1] |= 1 + + p.SetBytes(bytes) + if p.ProbablyPrime(20) { + return p, nil + } + } +} + +// Int returns a uniform random value in [0, max). It panics if max <= 0. +func Int(rand io.Reader, max *big.Int) (n *big.Int, err error) { + if max.Sign() <= 0 { + panic("crypto/rand: argument to Int is <= 0") + } + n = new(big.Int) + n.Sub(max, n.SetUint64(1)) + // bitLen is the maximum bit length needed to encode a value < max. + bitLen := n.BitLen() + if bitLen == 0 { + // the only valid result is 0 + return + } + // k is the maximum byte length needed to encode a value < max. + k := (bitLen + 7) / 8 + // b is the number of bits in the most significant byte of max-1. + b := uint(bitLen % 8) + if b == 0 { + b = 8 + } + + bytes := make([]byte, k) + + for { + _, err = io.ReadFull(rand, bytes) + if err != nil { + return nil, err + } + + // Clear bits in the first byte to increase the probability + // that the candidate is < max. + bytes[0] &= uint8(int(1< 0 && !isString && !prevString { - p.buf.writeByte(' ') - } - p.printArg(arg, 'v') - prevString = isString + prevString := false + for argNum, arg := range a { + isString := arg != nil && reflect.TypeOf(arg).Kind() == reflect.String + // Add a space between two non-string arguments. + if argNum > 0 && !isString && !prevString { + p.buf.writeByte(' ') } - */ - panic("todo: fmt.(*pp).doPrint") + p.printArg(arg, 'v') + prevString = isString + } } // doPrintln is like doPrint but always adds a space between arguments diff --git a/internal/lib/hash/crc32/crc32.go b/internal/lib/hash/crc32/crc32.go new file mode 100644 index 00000000..9acfcc2c --- /dev/null +++ b/internal/lib/hash/crc32/crc32.go @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 crc32 + +// llgo:skipall +import ( + "hash" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/zlib" +) + +// The size of a CRC-32 checksum in bytes. +const Size = 4 + +// Predefined polynomials. +const ( + // IEEE is by far and away the most common CRC-32 polynomial. + // Used by ethernet (IEEE 802.3), v.42, fddi, gzip, zip, png, ... + IEEE = 0xedb88320 + + // Castagnoli's polynomial, used in iSCSI. + // Has better error detection characteristics than IEEE. + // https://dx.doi.org/10.1109/26.231911 + Castagnoli = 0x82f63b78 + + // Koopman's polynomial. + // Also has better error detection characteristics than IEEE. + // https://dx.doi.org/10.1109/DSN.2002.1028931 + Koopman = 0xeb31d82e +) + +// Table is a 256-word table representing the polynomial for efficient processing. +type Table [256]uint32 + +// IEEETable is the table for the IEEE polynomial. +var IEEETable *Table = new(Table) + +// MakeTable returns a Table constructed from the specified polynomial. +// The contents of this Table must not be modified. +func MakeTable(poly uint32) *Table { + if poly == IEEE { + return IEEETable + } + panic("todo: hash/crc32.MakeTable") +} + +type digest uint32 + +func (d *digest) Size() int { return Size } + +func (d *digest) BlockSize() int { return 1 } + +func (d *digest) Reset() { *d = 0 } + +func (d *digest) Write(p []byte) (n int, err error) { + *d = digest(zlib.Crc32ZBytes(c.Ulong(*d), p)) + return len(p), nil +} + +func (d *digest) Sum32() uint32 { return uint32(*d) } + +func (d *digest) Sum(in []byte) []byte { + s := d.Sum32() + return append(in, byte(s>>24), byte(s>>16), byte(s>>8), byte(s)) +} + +func New(tab *Table) hash.Hash32 { + if tab == IEEETable { + return new(digest) + } + panic("todo: hash/crc32.New") +} + +// NewIEEE creates a new hash.Hash32 computing the CRC-32 checksum using +// the IEEE polynomial. Its Sum method will lay the value out in +// big-endian byte order. The returned Hash32 also implements +// encoding.BinaryMarshaler and encoding.BinaryUnmarshaler to marshal +// and unmarshal the internal state of the hash. +func NewIEEE() hash.Hash32 { return New(IEEETable) } + +// Update returns the result of adding the bytes in p to the crc. +func Update(crc uint32, tab *Table, p []byte) uint32 { + if tab == IEEETable { + return uint32(zlib.Crc32ZBytes(c.Ulong(crc), p)) + } + panic("todo: hash/crc32.Update") +} + +// Checksum returns the CRC-32 checksum of data +// using the polynomial represented by the Table. +func Checksum(data []byte, tab *Table) uint32 { return Update(0, tab, data) } + +// ChecksumIEEE returns the CRC-32 checksum of data +// using the IEEE polynomial. +func ChecksumIEEE(data []byte) uint32 { + return uint32(zlib.Crc32ZBytes(0, data)) +} diff --git a/internal/lib/internal/itoa/itoa.go b/internal/lib/internal/itoa/itoa.go new file mode 100644 index 00000000..32b15359 --- /dev/null +++ b/internal/lib/internal/itoa/itoa.go @@ -0,0 +1,34 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Simple conversions to avoid depending on strconv. + +// llgo:skipall +package itoa + +// Itoa converts val to a decimal string. +func Itoa(val int) string { + if val < 0 { + return "-" + Uitoa(uint(-val)) + } + return Uitoa(uint(val)) +} + +// Uitoa converts val to a decimal string. +func Uitoa(val uint) string { + if val == 0 { // avoid string allocation + return "0" + } + var buf [20]byte // big enough for 64bit value base 10 + i := len(buf) - 1 + for val >= 10 { + q := val / 10 + buf[i] = byte('0' + val - q*10) + i-- + val = q + } + // val < 10 + buf[i] = byte('0' + val) + return string(buf[i:]) +} diff --git a/internal/lib/io/pipe.go b/internal/lib/io/pipe.go new file mode 100644 index 00000000..b3ca0570 --- /dev/null +++ b/internal/lib/io/pipe.go @@ -0,0 +1,207 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Pipe adapter to connect code expecting an io.Reader +// with code expecting an io.Writer. + +package io + +import ( + "errors" + "io" + "sync" +) + +// onceError is an object that will only store an error once. +type onceError struct { + sync.Mutex // guards following + err error +} + +func (a *onceError) Store(err error) { + a.Lock() + defer a.Unlock() + if a.err != nil { + return + } + a.err = err +} +func (a *onceError) Load() error { + a.Lock() + defer a.Unlock() + return a.err +} + +// ErrClosedPipe is the error used for read or write operations on a closed pipe. +var ErrClosedPipe = errors.New("io: read/write on closed pipe") + +// A pipe is the shared pipe structure underlying PipeReader and PipeWriter. +type pipe struct { + wrMu sync.Mutex // Serializes Write operations + wrCh chan []byte + rdCh chan int + + once sync.Once // Protects closing done + done chan struct{} + rerr onceError + werr onceError +} + +func (p *pipe) read(b []byte) (n int, err error) { + select { + case <-p.done: + return 0, p.readCloseError() + default: + } + + select { + case bw := <-p.wrCh: + nr := copy(b, bw) + p.rdCh <- nr + return nr, nil + case <-p.done: + return 0, p.readCloseError() + } +} + +func (p *pipe) closeRead(err error) error { + if err == nil { + err = ErrClosedPipe + } + p.rerr.Store(err) + p.once.Do(func() { close(p.done) }) + return nil +} + +func (p *pipe) write(b []byte) (n int, err error) { + select { + case <-p.done: + return 0, p.writeCloseError() + default: + p.wrMu.Lock() + defer p.wrMu.Unlock() + } + + for once := true; once || len(b) > 0; once = false { + select { + case p.wrCh <- b: + nw := <-p.rdCh + b = b[nw:] + n += nw + case <-p.done: + return n, p.writeCloseError() + } + } + return n, nil +} + +func (p *pipe) closeWrite(err error) error { + if err == nil { + err = io.EOF + } + p.werr.Store(err) + p.once.Do(func() { close(p.done) }) + return nil +} + +// readCloseError is considered internal to the pipe type. +func (p *pipe) readCloseError() error { + rerr := p.rerr.Load() + if werr := p.werr.Load(); rerr == nil && werr != nil { + return werr + } + return ErrClosedPipe +} + +// writeCloseError is considered internal to the pipe type. +func (p *pipe) writeCloseError() error { + werr := p.werr.Load() + if rerr := p.rerr.Load(); werr == nil && rerr != nil { + return rerr + } + return ErrClosedPipe +} + +// A PipeReader is the read half of a pipe. +type PipeReader struct { + p *pipe +} + +// Read implements the standard Read interface: +// it reads data from the pipe, blocking until a writer +// arrives or the write end is closed. +// If the write end is closed with an error, that error is +// returned as err; otherwise err is EOF. +func (r *PipeReader) Read(data []byte) (n int, err error) { + return r.p.read(data) +} + +// Close closes the reader; subsequent writes to the +// write half of the pipe will return the error ErrClosedPipe. +func (r *PipeReader) Close() error { + return r.CloseWithError(nil) +} + +// CloseWithError closes the reader; subsequent writes +// to the write half of the pipe will return the error err. +// +// CloseWithError never overwrites the previous error if it exists +// and always returns nil. +func (r *PipeReader) CloseWithError(err error) error { + return r.p.closeRead(err) +} + +// A PipeWriter is the write half of a pipe. +type PipeWriter struct { + p *pipe +} + +// Write implements the standard Write interface: +// it writes data to the pipe, blocking until one or more readers +// have consumed all the data or the read end is closed. +// If the read end is closed with an error, that err is +// returned as err; otherwise err is ErrClosedPipe. +func (w *PipeWriter) Write(data []byte) (n int, err error) { + return w.p.write(data) +} + +// Close closes the writer; subsequent reads from the +// read half of the pipe will return no bytes and EOF. +func (w *PipeWriter) Close() error { + return w.CloseWithError(nil) +} + +// CloseWithError closes the writer; subsequent reads from the +// read half of the pipe will return no bytes and the error err, +// or EOF if err is nil. +// +// CloseWithError never overwrites the previous error if it exists +// and always returns nil. +func (w *PipeWriter) CloseWithError(err error) error { + return w.p.closeWrite(err) +} + +// Pipe creates a synchronous in-memory pipe. +// It can be used to connect code expecting an io.Reader +// with code expecting an io.Writer. +// +// Reads and Writes on the pipe are matched one to one +// except when multiple Reads are needed to consume a single Write. +// That is, each Write to the PipeWriter blocks until it has satisfied +// one or more Reads from the PipeReader that fully consume +// the written data. +// The data is copied directly from the Write to the corresponding +// Read (or Reads); there is no internal buffering. +// +// It is safe to call Read and Write in parallel with each other or with Close. +// Parallel calls to Read and parallel calls to Write are also safe: +// the individual calls will be gated sequentially. +func Pipe() (*PipeReader, *PipeWriter) { + p := &pipe{ + wrCh: make(chan []byte, 1), + rdCh: make(chan int, 1), + done: make(chan struct{}, 1), + } + return &PipeReader{p}, &PipeWriter{p} +} diff --git a/internal/lib/math/big/int.go b/internal/lib/math/big/int.go new file mode 100644 index 00000000..f568938b --- /dev/null +++ b/internal/lib/math/big/int.go @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 big + +// llgo:skipall +import ( + "github.com/goplus/llgo/c/openssl" +) + +// A Word represents a single digit of a multi-precision unsigned integer. +type Word openssl.BN_ULONG + +// ----------------------------------------------------------------------------- + +// TODO(xsw): share ctx +func ctxGet() *openssl.BN_CTX { + return openssl.BN_CTXNew() +} + +func ctxPut(ctx *openssl.BN_CTX) { + ctx.Free() +} + +// ----------------------------------------------------------------------------- + +type Int openssl.BIGNUM + +// Sign returns: +// +// -1 if x < 0 +// 0 if x == 0 +// +1 if x > 0 +func (x *Int) Sign() int { + a := (*openssl.BIGNUM)(x) + if a.IsNegative() != 0 { + return -1 + } else if a.IsZero() != 0 { + return 0 + } + return 1 +} + +// SetInt64 sets z to x and returns z. +func (z *Int) SetInt64(x int64) *Int { + a := (*openssl.BIGNUM)(z) + if x < 0 { + a.SetWord(openssl.BN_ULONG(-x)) + a.SetNegative(1) + } else { + a.SetWord(openssl.BN_ULONG(x)) + a.SetNegative(0) + } + return z +} + +// SetUint64 sets z to x and returns z. +func (z *Int) SetUint64(x uint64) *Int { + a := (*openssl.BIGNUM)(z) + a.SetWord(openssl.BN_ULONG(x)) + a.SetNegative(0) + return z +} + +// NewInt allocates and returns a new Int set to x. +func NewInt(x int64) *Int { + z := (*Int)(openssl.BNNew()) + return z.SetInt64(x) +} + +/* +// Set sets z to x and returns z. +func (z *Int) Set(x *Int) *Int { +} + +// Bits provides raw (unchecked but fast) access to x by returning its +// absolute value as a little-endian Word slice. The result and x share +// the same underlying array. +// Bits is intended to support implementation of missing low-level Int +// functionality outside this package; it should be avoided otherwise. +func (x *Int) Bits() []Word { +} + +// SetBits provides raw (unchecked but fast) access to z by setting its +// value to abs, interpreted as a little-endian Word slice, and returning +// z. The result and abs share the same underlying array. +// SetBits is intended to support implementation of missing low-level Int +// functionality outside this package; it should be avoided otherwise. +func (z *Int) SetBits(abs []Word) *Int { +} + +// Abs sets z to |x| (the absolute value of x) and returns z. +func (z *Int) Abs(x *Int) *Int { +} + +// Neg sets z to -x and returns z. +func (z *Int) Neg(x *Int) *Int { +} +*/ + +// Add sets z to the sum x+y and returns z. +func (z *Int) Add(x, y *Int) *Int { + (*openssl.BIGNUM)(z).Add((*openssl.BIGNUM)(x), (*openssl.BIGNUM)(y)) + return z +} + +// Sub sets z to the difference x-y and returns z. +func (z *Int) Sub(x, y *Int) *Int { + (*openssl.BIGNUM)(z).Sub((*openssl.BIGNUM)(x), (*openssl.BIGNUM)(y)) + return z +} + +/* +// Mul sets z to the product x*y and returns z. +func (z *Int) Mul(x, y *Int) *Int { +} + +// MulRange sets z to the product of all integers +// in the range [a, b] inclusively and returns z. +// If a > b (empty range), the result is 1. +func (z *Int) MulRange(a, b int64) *Int { +} + +// Binomial sets z to the binomial coefficient C(n, k) and returns z. +func (z *Int) Binomial(n, k int64) *Int { +} + +// Quo sets z to the quotient x/y for y != 0 and returns z. +// If y == 0, a division-by-zero run-time panic occurs. +// Quo implements truncated division (like Go); see QuoRem for more details. +func (z *Int) Quo(x, y *Int) *Int { +} + +// Rem sets z to the remainder x%y for y != 0 and returns z. +// If y == 0, a division-by-zero run-time panic occurs. +// Rem implements truncated modulus (like Go); see QuoRem for more details. +func (z *Int) Rem(x, y *Int) *Int { +} + +// QuoRem sets z to the quotient x/y and r to the remainder x%y +// and returns the pair (z, r) for y != 0. +// If y == 0, a division-by-zero run-time panic occurs. +// +// QuoRem implements T-division and modulus (like Go): +// +// q = x/y with the result truncated to zero +// r = x - y*q +// +// (See Daan Leijen, “Division and Modulus for Computer Scientists”.) +// See DivMod for Euclidean division and modulus (unlike Go). +func (z *Int) QuoRem(x, y, r *Int) (*Int, *Int) { +} + +// Div sets z to the quotient x/y for y != 0 and returns z. +// If y == 0, a division-by-zero run-time panic occurs. +// Div implements Euclidean division (unlike Go); see DivMod for more details. +func (z *Int) Div(x, y *Int) *Int { +} + +// Mod sets z to the modulus x%y for y != 0 and returns z. +// If y == 0, a division-by-zero run-time panic occurs. +// Mod implements Euclidean modulus (unlike Go); see DivMod for more details. +func (z *Int) Mod(x, y *Int) *Int { +} + +// DivMod sets z to the quotient x div y and m to the modulus x mod y +// and returns the pair (z, m) for y != 0. +// If y == 0, a division-by-zero run-time panic occurs. +// +// DivMod implements Euclidean division and modulus (unlike Go): +// +// q = x div y such that +// m = x - y*q with 0 <= m < |y| +// +// (See Raymond T. Boute, “The Euclidean definition of the functions +// div and mod”. ACM Transactions on Programming Languages and +// Systems (TOPLAS), 14(2):127-144, New York, NY, USA, 4/1992. +// ACM press.) +// See QuoRem for T-division and modulus (like Go). +func (z *Int) DivMod(x, y, m *Int) (*Int, *Int) { +} +*/ + +// Cmp compares x and y and returns: +// +// -1 if x < y +// 0 if x == y +// +1 if x > y +func (x *Int) Cmp(y *Int) (r int) { + return int((*openssl.BIGNUM)(x).Cmp((*openssl.BIGNUM)(y))) +} + +// CmpAbs compares the absolute values of x and y and returns: +// +// -1 if |x| < |y| +// 0 if |x| == |y| +// +1 if |x| > |y| +func (x *Int) CmpAbs(y *Int) int { + return int((*openssl.BIGNUM)(x).Ucmp((*openssl.BIGNUM)(y))) +} + +/* +// Int64 returns the int64 representation of x. +// If x cannot be represented in an int64, the result is undefined. +func (x *Int) Int64() int64 { +} + +// Uint64 returns the uint64 representation of x. +// If x cannot be represented in a uint64, the result is undefined. +func (x *Int) Uint64() uint64 { +} + +// IsInt64 reports whether x can be represented as an int64. +func (x *Int) IsInt64() bool { +} + +// IsUint64 reports whether x can be represented as a uint64. +func (x *Int) IsUint64() bool { +} + +// Float64 returns the float64 value nearest x, +// and an indication of any rounding that occurred. +// TODO(xsw): +// func (x *Int) Float64() (float64, Accuracy) + +// SetString sets z to the value of s, interpreted in the given base, +// and returns z and a boolean indicating success. The entire string +// (not just a prefix) must be valid for success. If SetString fails, +// the value of z is undefined but the returned value is nil. +// +// The base argument must be 0 or a value between 2 and MaxBase. +// For base 0, the number prefix determines the actual base: A prefix of +// “0b” or “0B” selects base 2, “0”, “0o” or “0O” selects base 8, +// and “0x” or “0X” selects base 16. Otherwise, the selected base is 10 +// and no prefix is accepted. +// +// For bases <= 36, lower and upper case letters are considered the same: +// The letters 'a' to 'z' and 'A' to 'Z' represent digit values 10 to 35. +// For bases > 36, the upper case letters 'A' to 'Z' represent the digit +// values 36 to 61. +// +// For base 0, an underscore character “_” may appear between a base +// prefix and an adjacent digit, and between successive digits; such +// underscores do not change the value of the number. +// Incorrect placement of underscores is reported as an error if there +// are no other errors. If base != 0, underscores are not recognized +// and act like any other character that is not a valid digit. +func (z *Int) SetString(s string, base int) (*Int, bool) { +} + +// SetBytes interprets buf as the bytes of a big-endian unsigned +// integer, sets z to that value, and returns z. +func (z *Int) SetBytes(buf []byte) *Int { +} + +// Bytes returns the absolute value of x as a big-endian byte slice. +// +// To use a fixed length slice, or a preallocated one, use FillBytes. +func (x *Int) Bytes() []byte { +} + +// FillBytes sets buf to the absolute value of x, storing it as a zero-extended +// big-endian byte slice, and returns buf. +// +// If the absolute value of x doesn't fit in buf, FillBytes will panic. +func (x *Int) FillBytes(buf []byte) []byte { +} + +// BitLen returns the length of the absolute value of x in bits. +// The bit length of 0 is 0. +func (x *Int) BitLen() int { +} + +// TrailingZeroBits returns the number of consecutive least significant zero +// bits of |x|. +func (x *Int) TrailingZeroBits() uint { +} +*/ + +// Exp sets z = x**y mod |m| (i.e. the sign of m is ignored), and returns z. +// If m == nil or m == 0, z = x**y unless y <= 0 then z = 1. If m != 0, y < 0, +// and x and m are not relatively prime, z is unchanged and nil is returned. +// +// Modular exponentiation of inputs of a particular size is not a +// cryptographically constant-time operation. +func (z *Int) Exp(x, y, m *Int) *Int { + ctx := ctxGet() + mbn := (*openssl.BIGNUM)(m) + if mbn == nil || mbn.IsZero() != 0 { + (*openssl.BIGNUM)(z).Exp((*openssl.BIGNUM)(x), (*openssl.BIGNUM)(y), ctx) + } else { + (*openssl.BIGNUM)(z).ModExp((*openssl.BIGNUM)(x), (*openssl.BIGNUM)(y), mbn, ctx) + } + ctxPut(ctx) + return z +} + +/* +// GCD sets z to the greatest common divisor of a and b and returns z. +// If x or y are not nil, GCD sets their value such that z = a*x + b*y. +// +// a and b may be positive, zero or negative. (Before Go 1.14 both had +// to be > 0.) Regardless of the signs of a and b, z is always >= 0. +// +// If a == b == 0, GCD sets z = x = y = 0. +// +// If a == 0 and b != 0, GCD sets z = |b|, x = 0, y = sign(b) * 1. +// +// If a != 0 and b == 0, GCD sets z = |a|, x = sign(a) * 1, y = 0. +func (z *Int) GCD(x, y, a, b *Int) *Int { +} + +// Rand sets z to a pseudo-random number in [0, n) and returns z. +// +// As this uses the math/rand package, it must not be used for +// security-sensitive work. Use crypto/rand.Int instead. +func (z *Int) Rand(rnd *rand.Rand, n *Int) *Int { +} + +// ModInverse sets z to the multiplicative inverse of g in the ring ℤ/nℤ +// and returns z. If g and n are not relatively prime, g has no multiplicative +// inverse in the ring ℤ/nℤ. In this case, z is unchanged and the return value +// is nil. If n == 0, a division-by-zero run-time panic occurs. +func (z *Int) ModInverse(g, n *Int) *Int { +} + +// Jacobi returns the Jacobi symbol (x/y), either +1, -1, or 0. +// The y argument must be an odd integer. +func Jacobi(x, y *Int) int { +} + +// ModSqrt sets z to a square root of x mod p if such a square root exists, and +// returns z. The modulus p must be an odd prime. If x is not a square mod p, +// ModSqrt leaves z unchanged and returns nil. This function panics if p is +// not an odd integer, its behavior is undefined if p is odd but not prime. +func (z *Int) ModSqrt(x, p *Int) *Int { +} + +// Lsh sets z = x << n and returns z. +func (z *Int) Lsh(x *Int, n uint) *Int { +} + +// Rsh sets z = x >> n and returns z. +func (z *Int) Rsh(x *Int, n uint) *Int { +} + +// Bit returns the value of the i'th bit of x. That is, it +// returns (x>>i)&1. The bit index i must be >= 0. +func (x *Int) Bit(i int) uint { +} + +// SetBit sets z to x, with x's i'th bit set to b (0 or 1). +// That is, if b is 1 SetBit sets z = x | (1 << i); +// if b is 0 SetBit sets z = x &^ (1 << i). If b is not 0 or 1, +// SetBit will panic. +func (z *Int) SetBit(x *Int, i int, b uint) *Int { +} + +// And sets z = x & y and returns z. +func (z *Int) And(x, y *Int) *Int { +} + +// AndNot sets z = x &^ y and returns z. +func (z *Int) AndNot(x, y *Int) *Int { +} + +// Or sets z = x | y and returns z. +func (z *Int) Or(x, y *Int) *Int { +} + +// Xor sets z = x ^ y and returns z. +func (z *Int) Xor(x, y *Int) *Int { +} + +// Not sets z = ^x and returns z. +func (z *Int) Not(x *Int) *Int { +} + +// Sqrt sets z to ⌊√x⌋, the largest integer such that z² ≤ x, and returns z. +// It panics if x is negative. +func (z *Int) Sqrt(x *Int) *Int { +} +*/ + +// ----------------------------------------------------------------------------- diff --git a/internal/lib/math/big/intconv.go b/internal/lib/math/big/intconv.go new file mode 100644 index 00000000..4b946be3 --- /dev/null +++ b/internal/lib/math/big/intconv.go @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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 big + +import ( + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/openssl" +) + +/* +// Text returns the string representation of x in the given base. +// Base must be between 2 and 62, inclusive. The result uses the +// lower-case letters 'a' to 'z' for digit values 10 to 35, and +// the upper-case letters 'A' to 'Z' for digit values 36 to 61. +// No prefix (such as "0x") is added to the string. If x is a nil +// pointer it returns "". +func (x *Int) Text(base int) string { +} + +// Append appends the string representation of x, as generated by +// x.Text(base), to buf and returns the extended buffer. +func (x *Int) Append(buf []byte, base int) []byte { +} +*/ + +// String returns the decimal representation of x as generated by +// x.Text(10). +func (x *Int) String() string { + // TODO(xsw): can optimize it? + cstr := (*openssl.BIGNUM)(x).CStr() + ret := c.GoString(cstr) + openssl.FreeCStr(cstr) + return ret +} + +/* +// Format implements fmt.Formatter. It accepts the formats +// 'b' (binary), 'o' (octal with 0 prefix), 'O' (octal with 0o prefix), +// 'd' (decimal), 'x' (lowercase hexadecimal), and +// 'X' (uppercase hexadecimal). +// Also supported are the full suite of package fmt's format +// flags for integral types, including '+' and ' ' for sign +// control, '#' for leading zero in octal and for hexadecimal, +// a leading "0x" or "0X" for "%#x" and "%#X" respectively, +// specification of minimum digits precision, output field +// width, space or zero padding, and '-' for left or right +// justification. +func (x *Int) Format(s fmt.State, ch rune) { +} + +// Scan is a support routine for fmt.Scanner; it sets z to the value of +// the scanned number. It accepts the formats 'b' (binary), 'o' (octal), +// 'd' (decimal), 'x' (lowercase hexadecimal), and 'X' (uppercase hexadecimal). +func (z *Int) Scan(s fmt.ScanState, ch rune) error { +} +*/ diff --git a/internal/lib/math/rand/exp.go b/internal/lib/math/rand/exp.go new file mode 100644 index 00000000..c1162c19 --- /dev/null +++ b/internal/lib/math/rand/exp.go @@ -0,0 +1,221 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rand + +import ( + "math" +) + +/* + * Exponential distribution + * + * See "The Ziggurat Method for Generating Random Variables" + * (Marsaglia & Tsang, 2000) + * https://www.jstatsoft.org/v05/i08/paper [pdf] + */ + +const ( + re = 7.69711747013104972 +) + +// ExpFloat64 returns an exponentially distributed float64 in the range +// (0, +math.MaxFloat64] with an exponential distribution whose rate parameter +// (lambda) is 1 and whose mean is 1/lambda (1). +// To produce a distribution with a different rate parameter, +// callers can adjust the output using: +// +// sample = ExpFloat64() / desiredRateParameter +func (r *Rand) ExpFloat64() float64 { + for { + j := r.Uint32() + i := j & 0xFF + x := float64(j) * float64(we[i]) + if j < ke[i] { + return x + } + if i == 0 { + return re - math.Log(r.Float64()) + } + if fe[i]+float32(r.Float64())*(fe[i-1]-fe[i]) < float32(math.Exp(-x)) { + return x + } + } +} + +var ke = [256]uint32{ + 0xe290a139, 0x0, 0x9beadebc, 0xc377ac71, 0xd4ddb990, + 0xde893fb8, 0xe4a8e87c, 0xe8dff16a, 0xebf2deab, 0xee49a6e8, + 0xf0204efd, 0xf19bdb8e, 0xf2d458bb, 0xf3da104b, 0xf4b86d78, + 0xf577ad8a, 0xf61de83d, 0xf6afb784, 0xf730a573, 0xf7a37651, + 0xf80a5bb6, 0xf867189d, 0xf8bb1b4f, 0xf9079062, 0xf94d70ca, + 0xf98d8c7d, 0xf9c8928a, 0xf9ff175b, 0xfa319996, 0xfa6085f8, + 0xfa8c3a62, 0xfab5084e, 0xfadb36c8, 0xfaff0410, 0xfb20a6ea, + 0xfb404fb4, 0xfb5e2951, 0xfb7a59e9, 0xfb95038c, 0xfbae44ba, + 0xfbc638d8, 0xfbdcf892, 0xfbf29a30, 0xfc0731df, 0xfc1ad1ed, + 0xfc2d8b02, 0xfc3f6c4d, 0xfc5083ac, 0xfc60ddd1, 0xfc708662, + 0xfc7f8810, 0xfc8decb4, 0xfc9bbd62, 0xfca9027c, 0xfcb5c3c3, + 0xfcc20864, 0xfccdd70a, 0xfcd935e3, 0xfce42ab0, 0xfceebace, + 0xfcf8eb3b, 0xfd02c0a0, 0xfd0c3f59, 0xfd156b7b, 0xfd1e48d6, + 0xfd26daff, 0xfd2f2552, 0xfd372af7, 0xfd3eeee5, 0xfd4673e7, + 0xfd4dbc9e, 0xfd54cb85, 0xfd5ba2f2, 0xfd62451b, 0xfd68b415, + 0xfd6ef1da, 0xfd750047, 0xfd7ae120, 0xfd809612, 0xfd8620b4, + 0xfd8b8285, 0xfd90bcf5, 0xfd95d15e, 0xfd9ac10b, 0xfd9f8d36, + 0xfda43708, 0xfda8bf9e, 0xfdad2806, 0xfdb17141, 0xfdb59c46, + 0xfdb9a9fd, 0xfdbd9b46, 0xfdc170f6, 0xfdc52bd8, 0xfdc8ccac, + 0xfdcc542d, 0xfdcfc30b, 0xfdd319ef, 0xfdd6597a, 0xfdd98245, + 0xfddc94e5, 0xfddf91e6, 0xfde279ce, 0xfde54d1f, 0xfde80c52, + 0xfdeab7de, 0xfded5034, 0xfdefd5be, 0xfdf248e3, 0xfdf4aa06, + 0xfdf6f984, 0xfdf937b6, 0xfdfb64f4, 0xfdfd818d, 0xfdff8dd0, + 0xfe018a08, 0xfe03767a, 0xfe05536c, 0xfe07211c, 0xfe08dfc9, + 0xfe0a8fab, 0xfe0c30fb, 0xfe0dc3ec, 0xfe0f48b1, 0xfe10bf76, + 0xfe122869, 0xfe1383b4, 0xfe14d17c, 0xfe1611e7, 0xfe174516, + 0xfe186b2a, 0xfe19843e, 0xfe1a9070, 0xfe1b8fd6, 0xfe1c8289, + 0xfe1d689b, 0xfe1e4220, 0xfe1f0f26, 0xfe1fcfbc, 0xfe2083ed, + 0xfe212bc3, 0xfe21c745, 0xfe225678, 0xfe22d95f, 0xfe234ffb, + 0xfe23ba4a, 0xfe241849, 0xfe2469f2, 0xfe24af3c, 0xfe24e81e, + 0xfe25148b, 0xfe253474, 0xfe2547c7, 0xfe254e70, 0xfe25485a, + 0xfe25356a, 0xfe251586, 0xfe24e88f, 0xfe24ae64, 0xfe2466e1, + 0xfe2411df, 0xfe23af34, 0xfe233eb4, 0xfe22c02c, 0xfe22336b, + 0xfe219838, 0xfe20ee58, 0xfe20358c, 0xfe1f6d92, 0xfe1e9621, + 0xfe1daef0, 0xfe1cb7ac, 0xfe1bb002, 0xfe1a9798, 0xfe196e0d, + 0xfe1832fd, 0xfe16e5fe, 0xfe15869d, 0xfe141464, 0xfe128ed3, + 0xfe10f565, 0xfe0f478c, 0xfe0d84b1, 0xfe0bac36, 0xfe09bd73, + 0xfe07b7b5, 0xfe059a40, 0xfe03644c, 0xfe011504, 0xfdfeab88, + 0xfdfc26e9, 0xfdf98629, 0xfdf6c83b, 0xfdf3ec01, 0xfdf0f04a, + 0xfdedd3d1, 0xfdea953d, 0xfde7331e, 0xfde3abe9, 0xfddffdfb, + 0xfddc2791, 0xfdd826cd, 0xfdd3f9a8, 0xfdcf9dfc, 0xfdcb1176, + 0xfdc65198, 0xfdc15bb3, 0xfdbc2ce2, 0xfdb6c206, 0xfdb117be, + 0xfdab2a63, 0xfda4f5fd, 0xfd9e7640, 0xfd97a67a, 0xfd908192, + 0xfd8901f2, 0xfd812182, 0xfd78d98e, 0xfd7022bb, 0xfd66f4ed, + 0xfd5d4732, 0xfd530f9c, 0xfd48432b, 0xfd3cd59a, 0xfd30b936, + 0xfd23dea4, 0xfd16349e, 0xfd07a7a3, 0xfcf8219b, 0xfce7895b, + 0xfcd5c220, 0xfcc2aadb, 0xfcae1d5e, 0xfc97ed4e, 0xfc7fe6d4, + 0xfc65ccf3, 0xfc495762, 0xfc2a2fc8, 0xfc07ee19, 0xfbe213c1, + 0xfbb8051a, 0xfb890078, 0xfb5411a5, 0xfb180005, 0xfad33482, + 0xfa839276, 0xfa263b32, 0xf9b72d1c, 0xf930a1a2, 0xf889f023, + 0xf7b577d2, 0xf69c650c, 0xf51530f0, 0xf2cb0e3c, 0xeeefb15d, + 0xe6da6ecf, +} +var we = [256]float32{ + 2.0249555e-09, 1.486674e-11, 2.4409617e-11, 3.1968806e-11, + 3.844677e-11, 4.4228204e-11, 4.9516443e-11, 5.443359e-11, + 5.905944e-11, 6.344942e-11, 6.7643814e-11, 7.1672945e-11, + 7.556032e-11, 7.932458e-11, 8.298079e-11, 8.654132e-11, + 9.0016515e-11, 9.3415074e-11, 9.674443e-11, 1.0001099e-10, + 1.03220314e-10, 1.06377254e-10, 1.09486115e-10, 1.1255068e-10, + 1.1557435e-10, 1.1856015e-10, 1.2151083e-10, 1.2442886e-10, + 1.2731648e-10, 1.3017575e-10, 1.3300853e-10, 1.3581657e-10, + 1.3860142e-10, 1.4136457e-10, 1.4410738e-10, 1.4683108e-10, + 1.4953687e-10, 1.5222583e-10, 1.54899e-10, 1.5755733e-10, + 1.6020171e-10, 1.6283301e-10, 1.6545203e-10, 1.6805951e-10, + 1.7065617e-10, 1.732427e-10, 1.7581973e-10, 1.7838787e-10, + 1.8094774e-10, 1.8349985e-10, 1.8604476e-10, 1.8858298e-10, + 1.9111498e-10, 1.9364126e-10, 1.9616223e-10, 1.9867835e-10, + 2.0119004e-10, 2.0369768e-10, 2.0620168e-10, 2.087024e-10, + 2.1120022e-10, 2.136955e-10, 2.1618855e-10, 2.1867974e-10, + 2.2116936e-10, 2.2365775e-10, 2.261452e-10, 2.2863202e-10, + 2.311185e-10, 2.3360494e-10, 2.360916e-10, 2.3857874e-10, + 2.4106667e-10, 2.4355562e-10, 2.4604588e-10, 2.485377e-10, + 2.5103128e-10, 2.5352695e-10, 2.560249e-10, 2.585254e-10, + 2.6102867e-10, 2.6353494e-10, 2.6604446e-10, 2.6855745e-10, + 2.7107416e-10, 2.7359479e-10, 2.761196e-10, 2.7864877e-10, + 2.8118255e-10, 2.8372119e-10, 2.8626485e-10, 2.888138e-10, + 2.9136826e-10, 2.939284e-10, 2.9649452e-10, 2.9906677e-10, + 3.016454e-10, 3.0423064e-10, 3.0682268e-10, 3.0942177e-10, + 3.1202813e-10, 3.1464195e-10, 3.1726352e-10, 3.19893e-10, + 3.2253064e-10, 3.251767e-10, 3.2783135e-10, 3.3049485e-10, + 3.3316744e-10, 3.3584938e-10, 3.3854083e-10, 3.4124212e-10, + 3.4395342e-10, 3.46675e-10, 3.4940711e-10, 3.5215003e-10, + 3.5490397e-10, 3.5766917e-10, 3.6044595e-10, 3.6323455e-10, + 3.660352e-10, 3.6884823e-10, 3.7167386e-10, 3.745124e-10, + 3.773641e-10, 3.802293e-10, 3.8310827e-10, 3.860013e-10, + 3.8890866e-10, 3.918307e-10, 3.9476775e-10, 3.9772008e-10, + 4.0068804e-10, 4.0367196e-10, 4.0667217e-10, 4.09689e-10, + 4.1272286e-10, 4.1577405e-10, 4.1884296e-10, 4.2192994e-10, + 4.250354e-10, 4.281597e-10, 4.313033e-10, 4.3446652e-10, + 4.3764986e-10, 4.408537e-10, 4.4407847e-10, 4.4732465e-10, + 4.5059267e-10, 4.5388301e-10, 4.571962e-10, 4.6053267e-10, + 4.6389292e-10, 4.6727755e-10, 4.70687e-10, 4.741219e-10, + 4.7758275e-10, 4.810702e-10, 4.845848e-10, 4.8812715e-10, + 4.9169796e-10, 4.9529775e-10, 4.989273e-10, 5.0258725e-10, + 5.0627835e-10, 5.100013e-10, 5.1375687e-10, 5.1754584e-10, + 5.21369e-10, 5.2522725e-10, 5.2912136e-10, 5.330522e-10, + 5.370208e-10, 5.4102806e-10, 5.45075e-10, 5.491625e-10, + 5.532918e-10, 5.5746385e-10, 5.616799e-10, 5.6594107e-10, + 5.7024857e-10, 5.746037e-10, 5.7900773e-10, 5.834621e-10, + 5.8796823e-10, 5.925276e-10, 5.971417e-10, 6.018122e-10, + 6.065408e-10, 6.113292e-10, 6.1617933e-10, 6.2109295e-10, + 6.260722e-10, 6.3111916e-10, 6.3623595e-10, 6.4142497e-10, + 6.4668854e-10, 6.5202926e-10, 6.5744976e-10, 6.6295286e-10, + 6.6854156e-10, 6.742188e-10, 6.79988e-10, 6.858526e-10, + 6.9181616e-10, 6.978826e-10, 7.04056e-10, 7.103407e-10, + 7.167412e-10, 7.2326256e-10, 7.2990985e-10, 7.366886e-10, + 7.4360473e-10, 7.5066453e-10, 7.5787476e-10, 7.6524265e-10, + 7.7277595e-10, 7.80483e-10, 7.883728e-10, 7.9645507e-10, + 8.047402e-10, 8.1323964e-10, 8.219657e-10, 8.309319e-10, + 8.401528e-10, 8.496445e-10, 8.594247e-10, 8.6951274e-10, + 8.799301e-10, 8.9070046e-10, 9.018503e-10, 9.134092e-10, + 9.254101e-10, 9.378904e-10, 9.508923e-10, 9.644638e-10, + 9.786603e-10, 9.935448e-10, 1.0091913e-09, 1.025686e-09, + 1.0431306e-09, 1.0616465e-09, 1.08138e-09, 1.1025096e-09, + 1.1252564e-09, 1.1498986e-09, 1.1767932e-09, 1.206409e-09, + 1.2393786e-09, 1.276585e-09, 1.3193139e-09, 1.3695435e-09, + 1.4305498e-09, 1.508365e-09, 1.6160854e-09, 1.7921248e-09, +} +var fe = [256]float32{ + 1, 0.9381437, 0.90046996, 0.87170434, 0.8477855, 0.8269933, + 0.8084217, 0.7915276, 0.77595687, 0.7614634, 0.7478686, + 0.7350381, 0.72286767, 0.71127474, 0.70019263, 0.6895665, + 0.67935055, 0.6695063, 0.66000086, 0.65080583, 0.6418967, + 0.63325197, 0.6248527, 0.6166822, 0.60872537, 0.60096896, + 0.5934009, 0.58601034, 0.5787874, 0.57172304, 0.5648092, + 0.5580383, 0.5514034, 0.5448982, 0.5385169, 0.53225386, + 0.5261042, 0.52006316, 0.5141264, 0.50828975, 0.5025495, + 0.496902, 0.49134386, 0.485872, 0.48048335, 0.4751752, + 0.46994483, 0.46478975, 0.45970762, 0.45469615, 0.44975325, + 0.44487688, 0.44006512, 0.43531612, 0.43062815, 0.42599955, + 0.42142874, 0.4169142, 0.41245446, 0.40804818, 0.403694, + 0.3993907, 0.39513698, 0.39093173, 0.38677382, 0.38266218, + 0.37859577, 0.37457356, 0.37059465, 0.3666581, 0.362763, + 0.35890847, 0.35509375, 0.351318, 0.3475805, 0.34388044, + 0.34021714, 0.3365899, 0.33299807, 0.32944095, 0.32591796, + 0.3224285, 0.3189719, 0.31554767, 0.31215525, 0.30879408, + 0.3054636, 0.3021634, 0.29889292, 0.2956517, 0.29243928, + 0.28925523, 0.28609908, 0.28297043, 0.27986884, 0.27679393, + 0.2737453, 0.2707226, 0.2677254, 0.26475343, 0.26180625, + 0.25888354, 0.25598502, 0.2531103, 0.25025907, 0.24743107, + 0.24462597, 0.24184346, 0.23908329, 0.23634516, 0.23362878, + 0.23093392, 0.2282603, 0.22560766, 0.22297576, 0.22036438, + 0.21777324, 0.21520215, 0.21265087, 0.21011916, 0.20760682, + 0.20511365, 0.20263945, 0.20018397, 0.19774707, 0.19532852, + 0.19292815, 0.19054577, 0.1881812, 0.18583426, 0.18350479, + 0.1811926, 0.17889754, 0.17661946, 0.17435817, 0.17211354, + 0.1698854, 0.16767362, 0.16547804, 0.16329853, 0.16113494, + 0.15898713, 0.15685499, 0.15473837, 0.15263714, 0.15055119, + 0.14848037, 0.14642459, 0.14438373, 0.14235765, 0.14034624, + 0.13834943, 0.13636707, 0.13439907, 0.13244532, 0.13050574, + 0.1285802, 0.12666863, 0.12477092, 0.12288698, 0.12101672, + 0.119160056, 0.1173169, 0.115487166, 0.11367077, 0.11186763, + 0.11007768, 0.10830083, 0.10653701, 0.10478614, 0.10304816, + 0.101323, 0.09961058, 0.09791085, 0.09622374, 0.09454919, + 0.09288713, 0.091237515, 0.08960028, 0.087975375, 0.08636274, + 0.08476233, 0.083174095, 0.081597984, 0.08003395, 0.07848195, + 0.076941945, 0.07541389, 0.07389775, 0.072393484, 0.07090106, + 0.069420435, 0.06795159, 0.066494495, 0.06504912, 0.063615434, + 0.062193416, 0.060783047, 0.059384305, 0.057997175, + 0.05662164, 0.05525769, 0.053905312, 0.052564494, 0.051235236, + 0.049917534, 0.048611384, 0.047316793, 0.046033762, 0.0447623, + 0.043502413, 0.042254124, 0.041017443, 0.039792392, + 0.038578995, 0.037377283, 0.036187284, 0.035009038, + 0.033842582, 0.032687962, 0.031545233, 0.030414443, 0.02929566, + 0.02818895, 0.027094385, 0.026012046, 0.024942026, 0.023884421, + 0.022839336, 0.021806888, 0.020787204, 0.019780423, 0.0187867, + 0.0178062, 0.016839107, 0.015885621, 0.014945968, 0.014020392, + 0.013109165, 0.012212592, 0.011331013, 0.01046481, 0.009614414, + 0.008780315, 0.007963077, 0.0071633533, 0.006381906, + 0.0056196423, 0.0048776558, 0.004157295, 0.0034602648, + 0.0027887989, 0.0021459677, 0.0015362998, 0.0009672693, + 0.00045413437, +} diff --git a/internal/lib/math/rand/normal.go b/internal/lib/math/rand/normal.go new file mode 100644 index 00000000..6654479a --- /dev/null +++ b/internal/lib/math/rand/normal.go @@ -0,0 +1,156 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rand + +import ( + "math" +) + +/* + * Normal distribution + * + * See "The Ziggurat Method for Generating Random Variables" + * (Marsaglia & Tsang, 2000) + * http://www.jstatsoft.org/v05/i08/paper [pdf] + */ + +const ( + rn = 3.442619855899 +) + +func absInt32(i int32) uint32 { + if i < 0 { + return uint32(-i) + } + return uint32(i) +} + +// NormFloat64 returns a normally distributed float64 in +// the range -math.MaxFloat64 through +math.MaxFloat64 inclusive, +// with standard normal distribution (mean = 0, stddev = 1). +// To produce a different normal distribution, callers can +// adjust the output using: +// +// sample = NormFloat64() * desiredStdDev + desiredMean +func (r *Rand) NormFloat64() float64 { + for { + j := int32(r.Uint32()) // Possibly negative + i := j & 0x7F + x := float64(j) * float64(wn[i]) + if absInt32(j) < kn[i] { + // This case should be hit better than 99% of the time. + return x + } + + if i == 0 { + // This extra work is only required for the base strip. + for { + x = -math.Log(r.Float64()) * (1.0 / rn) + y := -math.Log(r.Float64()) + if y+y >= x*x { + break + } + } + if j > 0 { + return rn + x + } + return -rn - x + } + if fn[i]+float32(r.Float64())*(fn[i-1]-fn[i]) < float32(math.Exp(-.5*x*x)) { + return x + } + } +} + +var kn = [128]uint32{ + 0x76ad2212, 0x0, 0x600f1b53, 0x6ce447a6, 0x725b46a2, + 0x7560051d, 0x774921eb, 0x789a25bd, 0x799045c3, 0x7a4bce5d, + 0x7adf629f, 0x7b5682a6, 0x7bb8a8c6, 0x7c0ae722, 0x7c50cce7, + 0x7c8cec5b, 0x7cc12cd6, 0x7ceefed2, 0x7d177e0b, 0x7d3b8883, + 0x7d5bce6c, 0x7d78dd64, 0x7d932886, 0x7dab0e57, 0x7dc0dd30, + 0x7dd4d688, 0x7de73185, 0x7df81cea, 0x7e07c0a3, 0x7e163efa, + 0x7e23b587, 0x7e303dfd, 0x7e3beec2, 0x7e46db77, 0x7e51155d, + 0x7e5aabb3, 0x7e63abf7, 0x7e6c222c, 0x7e741906, 0x7e7b9a18, + 0x7e82adfa, 0x7e895c63, 0x7e8fac4b, 0x7e95a3fb, 0x7e9b4924, + 0x7ea0a0ef, 0x7ea5b00d, 0x7eaa7ac3, 0x7eaf04f3, 0x7eb3522a, + 0x7eb765a5, 0x7ebb4259, 0x7ebeeafd, 0x7ec2620a, 0x7ec5a9c4, + 0x7ec8c441, 0x7ecbb365, 0x7ece78ed, 0x7ed11671, 0x7ed38d62, + 0x7ed5df12, 0x7ed80cb4, 0x7eda175c, 0x7edc0005, 0x7eddc78e, + 0x7edf6ebf, 0x7ee0f647, 0x7ee25ebe, 0x7ee3a8a9, 0x7ee4d473, + 0x7ee5e276, 0x7ee6d2f5, 0x7ee7a620, 0x7ee85c10, 0x7ee8f4cd, + 0x7ee97047, 0x7ee9ce59, 0x7eea0eca, 0x7eea3147, 0x7eea3568, + 0x7eea1aab, 0x7ee9e071, 0x7ee98602, 0x7ee90a88, 0x7ee86d08, + 0x7ee7ac6a, 0x7ee6c769, 0x7ee5bc9c, 0x7ee48a67, 0x7ee32efc, + 0x7ee1a857, 0x7edff42f, 0x7ede0ffa, 0x7edbf8d9, 0x7ed9ab94, + 0x7ed7248d, 0x7ed45fae, 0x7ed1585c, 0x7ece095f, 0x7eca6ccb, + 0x7ec67be2, 0x7ec22eee, 0x7ebd7d1a, 0x7eb85c35, 0x7eb2c075, + 0x7eac9c20, 0x7ea5df27, 0x7e9e769f, 0x7e964c16, 0x7e8d44ba, + 0x7e834033, 0x7e781728, 0x7e6b9933, 0x7e5d8a1a, 0x7e4d9ded, + 0x7e3b737a, 0x7e268c2f, 0x7e0e3ff5, 0x7df1aa5d, 0x7dcf8c72, + 0x7da61a1e, 0x7d72a0fb, 0x7d30e097, 0x7cd9b4ab, 0x7c600f1a, + 0x7ba90bdc, 0x7a722176, 0x77d664e5, +} +var wn = [128]float32{ + 1.7290405e-09, 1.2680929e-10, 1.6897518e-10, 1.9862688e-10, + 2.2232431e-10, 2.4244937e-10, 2.601613e-10, 2.7611988e-10, + 2.9073963e-10, 3.042997e-10, 3.1699796e-10, 3.289802e-10, + 3.4035738e-10, 3.5121603e-10, 3.616251e-10, 3.7164058e-10, + 3.8130857e-10, 3.9066758e-10, 3.9975012e-10, 4.08584e-10, + 4.1719309e-10, 4.2559822e-10, 4.338176e-10, 4.418672e-10, + 4.497613e-10, 4.5751258e-10, 4.651324e-10, 4.7263105e-10, + 4.8001775e-10, 4.87301e-10, 4.944885e-10, 5.015873e-10, + 5.0860405e-10, 5.155446e-10, 5.2241467e-10, 5.2921934e-10, + 5.359635e-10, 5.426517e-10, 5.4928817e-10, 5.5587696e-10, + 5.624219e-10, 5.6892646e-10, 5.753941e-10, 5.818282e-10, + 5.882317e-10, 5.946077e-10, 6.00959e-10, 6.072884e-10, + 6.135985e-10, 6.19892e-10, 6.2617134e-10, 6.3243905e-10, + 6.386974e-10, 6.449488e-10, 6.511956e-10, 6.5744005e-10, + 6.6368433e-10, 6.699307e-10, 6.7618144e-10, 6.824387e-10, + 6.8870465e-10, 6.949815e-10, 7.012715e-10, 7.075768e-10, + 7.1389966e-10, 7.202424e-10, 7.266073e-10, 7.329966e-10, + 7.394128e-10, 7.4585826e-10, 7.5233547e-10, 7.58847e-10, + 7.653954e-10, 7.719835e-10, 7.7861395e-10, 7.852897e-10, + 7.920138e-10, 7.987892e-10, 8.0561924e-10, 8.125073e-10, + 8.194569e-10, 8.2647167e-10, 8.3355556e-10, 8.407127e-10, + 8.479473e-10, 8.55264e-10, 8.6266755e-10, 8.7016316e-10, + 8.777562e-10, 8.8545243e-10, 8.932582e-10, 9.0117996e-10, + 9.09225e-10, 9.174008e-10, 9.2571584e-10, 9.341788e-10, + 9.427997e-10, 9.515889e-10, 9.605579e-10, 9.697193e-10, + 9.790869e-10, 9.88676e-10, 9.985036e-10, 1.0085882e-09, + 1.0189509e-09, 1.0296151e-09, 1.0406069e-09, 1.0519566e-09, + 1.063698e-09, 1.0758702e-09, 1.0885183e-09, 1.1016947e-09, + 1.1154611e-09, 1.1298902e-09, 1.1450696e-09, 1.1611052e-09, + 1.1781276e-09, 1.1962995e-09, 1.2158287e-09, 1.2369856e-09, + 1.2601323e-09, 1.2857697e-09, 1.3146202e-09, 1.347784e-09, + 1.3870636e-09, 1.4357403e-09, 1.5008659e-09, 1.6030948e-09, +} +var fn = [128]float32{ + 1, 0.9635997, 0.9362827, 0.9130436, 0.89228165, 0.87324303, + 0.8555006, 0.8387836, 0.8229072, 0.8077383, 0.793177, + 0.7791461, 0.7655842, 0.7524416, 0.73967725, 0.7272569, + 0.7151515, 0.7033361, 0.69178915, 0.68049186, 0.6694277, + 0.658582, 0.6479418, 0.63749546, 0.6272325, 0.6171434, + 0.6072195, 0.5974532, 0.58783704, 0.5783647, 0.56903, + 0.5598274, 0.5507518, 0.54179835, 0.5329627, 0.52424055, + 0.5156282, 0.50712204, 0.49871865, 0.49041483, 0.48220766, + 0.4740943, 0.46607214, 0.4581387, 0.45029163, 0.44252872, + 0.43484783, 0.427247, 0.41972435, 0.41227803, 0.40490642, + 0.39760786, 0.3903808, 0.3832238, 0.37613547, 0.36911446, + 0.3621595, 0.35526937, 0.34844297, 0.34167916, 0.33497685, + 0.3283351, 0.3217529, 0.3152294, 0.30876362, 0.30235484, + 0.29600215, 0.28970486, 0.2834622, 0.2772735, 0.27113807, + 0.2650553, 0.25902456, 0.2530453, 0.24711695, 0.241239, + 0.23541094, 0.22963232, 0.2239027, 0.21822165, 0.21258877, + 0.20700371, 0.20146611, 0.19597565, 0.19053204, 0.18513499, + 0.17978427, 0.17447963, 0.1692209, 0.16400786, 0.15884037, + 0.15371831, 0.14864157, 0.14361008, 0.13862377, 0.13368265, + 0.12878671, 0.12393598, 0.119130544, 0.11437051, 0.10965602, + 0.104987256, 0.10036444, 0.095787846, 0.0912578, 0.08677467, + 0.0823389, 0.077950984, 0.073611505, 0.06932112, 0.06508058, + 0.06089077, 0.056752663, 0.0526674, 0.048636295, 0.044660863, + 0.040742867, 0.03688439, 0.033087887, 0.029356318, + 0.025693292, 0.022103304, 0.018592102, 0.015167298, + 0.011839478, 0.008624485, 0.005548995, 0.0026696292, +} diff --git a/internal/lib/math/rand/rand.go b/internal/lib/math/rand/rand.go new file mode 100644 index 00000000..d5e80f89 --- /dev/null +++ b/internal/lib/math/rand/rand.go @@ -0,0 +1,547 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package rand implements pseudo-random number generators suitable for tasks +// such as simulation, but it should not be used for security-sensitive work. +// +// Random numbers are generated by a [Source], usually wrapped in a [Rand]. +// Both types should be used by a single goroutine at a time: sharing among +// multiple goroutines requires some kind of synchronization. +// +// Top-level functions, such as [Float64] and [Int], +// are safe for concurrent use by multiple goroutines. +// +// This package's outputs might be easily predictable regardless of how it's +// seeded. For random numbers suitable for security-sensitive work, see the +// crypto/rand package. +package rand + +// llgo:skipall +import ( + "sync" + "sync/atomic" + _ "unsafe" // for go:linkname + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/math/rand" + "github.com/goplus/llgo/c/time" +) + +// A Source represents a source of uniformly-distributed +// pseudo-random int64 values in the range [0, 1<<63). +// +// A Source is not safe for concurrent use by multiple goroutines. +type Source interface { + Int63() int64 + Seed(seed int64) +} + +// A Source64 is a Source that can also generate +// uniformly-distributed pseudo-random uint64 values in +// the range [0, 1<<64) directly. +// If a Rand r's underlying Source s implements Source64, +// then r.Uint64 returns the result of one call to s.Uint64 +// instead of making two calls to s.Int63. +type Source64 interface { + Source + Uint64() uint64 +} + +// NewSource returns a new pseudo-random Source seeded with the given value. +// Unlike the default Source used by top-level functions, this source is not +// safe for concurrent use by multiple goroutines. +// The returned Source implements Source64. +func NewSource(seed int64) Source { + return newSource(seed) +} + +func newSource(seed int64) *rngSource { + var rng rngSource + rng.Seed(seed) + return &rng +} + +// A Rand is a source of random numbers. +type Rand struct { + src Source + s64 Source64 // non-nil if src is source64 + + // readVal contains remainder of 63-bit integer used for bytes + // generation during most recent Read call. + // It is saved so next Read call can start where the previous + // one finished. + readVal int64 + // readPos indicates the number of low-order bytes of readVal + // that are still valid. + readPos int8 +} + +// New returns a new Rand that uses random values from src +// to generate other random values. +func New(src Source) *Rand { + s64, _ := src.(Source64) + return &Rand{src: src, s64: s64} +} + +// Seed uses the provided seed value to initialize the generator to a deterministic state. +// Seed should not be called concurrently with any other Rand method. +func (r *Rand) Seed(seed int64) { + if lk, ok := r.src.(*lockedSource); ok { + lk.seedPos(seed, &r.readPos) + return + } + + r.src.Seed(seed) + r.readPos = 0 +} + +// Int63 returns a non-negative pseudo-random 63-bit integer as an int64. +func (r *Rand) Int63() int64 { return r.src.Int63() } + +// Uint32 returns a pseudo-random 32-bit value as a uint32. +func (r *Rand) Uint32() uint32 { return uint32(r.Int63() >> 31) } + +// Uint64 returns a pseudo-random 64-bit value as a uint64. +func (r *Rand) Uint64() uint64 { + if r.s64 != nil { + return r.s64.Uint64() + } + return uint64(r.Int63())>>31 | uint64(r.Int63())<<32 +} + +// Int31 returns a non-negative pseudo-random 31-bit integer as an int32. +func (r *Rand) Int31() int32 { return int32(r.Int63() >> 32) } + +// Int returns a non-negative pseudo-random int. +func (r *Rand) Int() int { + u := uint(r.Int63()) + return int(u << 1 >> 1) // clear sign bit if int == int32 +} + +// Int63n returns, as an int64, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n <= 0. +func (r *Rand) Int63n(n int64) int64 { + if n <= 0 { + panic("invalid argument to Int63n") + } + if n&(n-1) == 0 { // n is power of two, can mask + return r.Int63() & (n - 1) + } + max := int64((1 << 63) - 1 - (1<<63)%uint64(n)) + v := r.Int63() + for v > max { + v = r.Int63() + } + return v % n +} + +// Int31n returns, as an int32, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n <= 0. +func (r *Rand) Int31n(n int32) int32 { + if n <= 0 { + panic("invalid argument to Int31n") + } + if n&(n-1) == 0 { // n is power of two, can mask + return r.Int31() & (n - 1) + } + max := int32((1 << 31) - 1 - (1<<31)%uint32(n)) + v := r.Int31() + for v > max { + v = r.Int31() + } + return v % n +} + +// int31n returns, as an int32, a non-negative pseudo-random number in the half-open interval [0,n). +// n must be > 0, but int31n does not check this; the caller must ensure it. +// int31n exists because Int31n is inefficient, but Go 1 compatibility +// requires that the stream of values produced by math/rand remain unchanged. +// int31n can thus only be used internally, by newly introduced APIs. +// +// For implementation details, see: +// https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction +// https://lemire.me/blog/2016/06/30/fast-random-shuffling +func (r *Rand) int31n(n int32) int32 { + v := r.Uint32() + prod := uint64(v) * uint64(n) + low := uint32(prod) + if low < uint32(n) { + thresh := uint32(-n) % uint32(n) + for low < thresh { + v = r.Uint32() + prod = uint64(v) * uint64(n) + low = uint32(prod) + } + } + return int32(prod >> 32) +} + +// Intn returns, as an int, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n <= 0. +func (r *Rand) Intn(n int) int { + if n <= 0 { + panic("invalid argument to Intn") + } + if n <= 1<<31-1 { + return int(r.Int31n(int32(n))) + } + return int(r.Int63n(int64(n))) +} + +// Float64 returns, as a float64, a pseudo-random number in the half-open interval [0.0,1.0). +func (r *Rand) Float64() float64 { + // A clearer, simpler implementation would be: + // return float64(r.Int63n(1<<53)) / (1<<53) + // However, Go 1 shipped with + // return float64(r.Int63()) / (1 << 63) + // and we want to preserve that value stream. + // + // There is one bug in the value stream: r.Int63() may be so close + // to 1<<63 that the division rounds up to 1.0, and we've guaranteed + // that the result is always less than 1.0. + // + // We tried to fix this by mapping 1.0 back to 0.0, but since float64 + // values near 0 are much denser than near 1, mapping 1 to 0 caused + // a theoretically significant overshoot in the probability of returning 0. + // Instead of that, if we round up to 1, just try again. + // Getting 1 only happens 1/2⁵³ of the time, so most clients + // will not observe it anyway. +again: + f := float64(r.Int63()) / (1 << 63) + if f == 1 { + goto again // resample; this branch is taken O(never) + } + return f +} + +// Float32 returns, as a float32, a pseudo-random number in the half-open interval [0.0,1.0). +func (r *Rand) Float32() float32 { + // Same rationale as in Float64: we want to preserve the Go 1 value + // stream except we want to fix it not to return 1.0 + // This only happens 1/2²⁴ of the time (plus the 1/2⁵³ of the time in Float64). +again: + f := float32(r.Float64()) + if f == 1 { + goto again // resample; this branch is taken O(very rarely) + } + return f +} + +// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers +// in the half-open interval [0,n). +func (r *Rand) Perm(n int) []int { + m := make([]int, n) + // In the following loop, the iteration when i=0 always swaps m[0] with m[0]. + // A change to remove this useless iteration is to assign 1 to i in the init + // statement. But Perm also effects r. Making this change will affect + // the final state of r. So this change can't be made for compatibility + // reasons for Go 1. + for i := 0; i < n; i++ { + j := r.Intn(i + 1) + m[i] = m[j] + m[j] = i + } + return m +} + +// Shuffle pseudo-randomizes the order of elements. +// n is the number of elements. Shuffle panics if n < 0. +// swap swaps the elements with indexes i and j. +func (r *Rand) Shuffle(n int, swap func(i, j int)) { + if n < 0 { + panic("invalid argument to Shuffle") + } + + // Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle + // Shuffle really ought not be called with n that doesn't fit in 32 bits. + // Not only will it take a very long time, but with 2³¹! possible permutations, + // there's no way that any PRNG can have a big enough internal state to + // generate even a minuscule percentage of the possible permutations. + // Nevertheless, the right API signature accepts an int n, so handle it as best we can. + i := n - 1 + for ; i > 1<<31-1-1; i-- { + j := int(r.Int63n(int64(i + 1))) + swap(i, j) + } + for ; i > 0; i-- { + j := int(r.int31n(int32(i + 1))) + swap(i, j) + } +} + +// Read generates len(p) random bytes and writes them into p. It +// always returns len(p) and a nil error. +// Read should not be called concurrently with any other Rand method. +func (r *Rand) Read(p []byte) (n int, err error) { + switch src := r.src.(type) { + case *lockedSource: + return src.read(p, &r.readVal, &r.readPos) + case *fastSource: + return src.read(p, &r.readVal, &r.readPos) + } + return read(p, r.src, &r.readVal, &r.readPos) +} + +func read(p []byte, src Source, readVal *int64, readPos *int8) (n int, err error) { + pos := *readPos + val := *readVal + rng, _ := src.(*rngSource) + for n = 0; n < len(p); n++ { + if pos == 0 { + if rng != nil { + val = rng.Int63() + } else { + val = src.Int63() + } + pos = 7 + } + p[n] = byte(val) + val >>= 8 + pos-- + } + *readPos = pos + *readVal = val + return +} + +/* + * Top-level convenience functions + */ + +// globalRandGenerator is the source of random numbers for the top-level +// convenience functions. When possible it uses the runtime fastrand64 +// function to avoid locking. This is not possible if the user called Seed, +// either explicitly or implicitly via GODEBUG=randautoseed=0. +var globalRandGenerator atomic.Pointer[Rand] + +// globalRand returns the generator to use for the top-level convenience +// functions. +func globalRand() *Rand { + if r := globalRandGenerator.Load(); r != nil { + return r + } + + // This is the first call. Initialize based on GODEBUG. + var r = &Rand{ + src: &fastSource{}, + s64: &fastSource{}, + } + if !globalRandGenerator.CompareAndSwap(nil, r) { + // Two different goroutines called some top-level + // function at the same time. While the results in + // that case are unpredictable, if we just use r here, + // and we are using a seed, we will most likely return + // the same value for both calls. That doesn't seem ideal. + // Just use the first one to get in. + return globalRandGenerator.Load() + } + return r +} + +func fastrand64() uint64 { + v1 := uint64(rand.Random()) + v2 := uint64(rand.Random()) + return v1 ^ (v2 << 32) +} + +func init() { + rand.Srandom(c.Uint(time.Time(nil))) +} + +// fastSource is an implementation of Source64 that uses the runtime +// fastrand functions. +type fastSource struct { + // The mutex is used to avoid race conditions in Read. + mu sync.Mutex +} + +func (*fastSource) Int63() int64 { + return int64(fastrand64() & rngMask) +} + +func (*fastSource) Seed(int64) { + panic("internal error: call to fastSource.Seed") +} + +func (*fastSource) Uint64() uint64 { + return fastrand64() +} + +func (fs *fastSource) read(p []byte, readVal *int64, readPos *int8) (n int, err error) { + fs.mu.Lock() + n, err = read(p, fs, readVal, readPos) + fs.mu.Unlock() + return +} + +// Seed uses the provided seed value to initialize the default Source to a +// deterministic state. Seed values that have the same remainder when +// divided by 2³¹-1 generate the same pseudo-random sequence. +// Seed, unlike the Rand.Seed method, is safe for concurrent use. +// +// If Seed is not called, the generator is seeded randomly at program startup. +// +// Prior to Go 1.20, the generator was seeded like Seed(1) at program startup. +// To force the old behavior, call Seed(1) at program startup. +// Alternately, set GODEBUG=randautoseed=0 in the environment +// before making any calls to functions in this package. +// +// Deprecated: As of Go 1.20 there is no reason to call Seed with +// a random value. Programs that call Seed with a known value to get +// a specific sequence of results should use New(NewSource(seed)) to +// obtain a local random generator. +func Seed(seed int64) { + orig := globalRandGenerator.Load() + + // If we are already using a lockedSource, we can just re-seed it. + if orig != nil { + if _, ok := orig.src.(*lockedSource); ok { + orig.Seed(seed) + return + } + } + + // Otherwise either + // 1) orig == nil, which is the normal case when Seed is the first + // top-level function to be called, or + // 2) orig is already a fastSource, in which case we need to change + // to a lockedSource. + // Either way we do the same thing. + + r := New(new(lockedSource)) + r.Seed(seed) + + if !globalRandGenerator.CompareAndSwap(orig, r) { + // Something changed underfoot. Retry to be safe. + Seed(seed) + } +} + +// Int63 returns a non-negative pseudo-random 63-bit integer as an int64 +// from the default Source. +func Int63() int64 { return globalRand().Int63() } + +// Uint32 returns a pseudo-random 32-bit value as a uint32 +// from the default Source. +func Uint32() uint32 { return globalRand().Uint32() } + +// Uint64 returns a pseudo-random 64-bit value as a uint64 +// from the default Source. +func Uint64() uint64 { return globalRand().Uint64() } + +// Int31 returns a non-negative pseudo-random 31-bit integer as an int32 +// from the default Source. +func Int31() int32 { return globalRand().Int31() } + +// Int returns a non-negative pseudo-random int from the default Source. +func Int() int { return globalRand().Int() } + +// Int63n returns, as an int64, a non-negative pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func Int63n(n int64) int64 { return globalRand().Int63n(n) } + +// Int31n returns, as an int32, a non-negative pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func Int31n(n int32) int32 { return globalRand().Int31n(n) } + +// Intn returns, as an int, a non-negative pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func Intn(n int) int { return globalRand().Intn(n) } + +// Float64 returns, as a float64, a pseudo-random number in the half-open interval [0.0,1.0) +// from the default Source. +func Float64() float64 { return globalRand().Float64() } + +// Float32 returns, as a float32, a pseudo-random number in the half-open interval [0.0,1.0) +// from the default Source. +func Float32() float32 { return globalRand().Float32() } + +// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers +// in the half-open interval [0,n) from the default Source. +func Perm(n int) []int { return globalRand().Perm(n) } + +// Shuffle pseudo-randomizes the order of elements using the default Source. +// n is the number of elements. Shuffle panics if n < 0. +// swap swaps the elements with indexes i and j. +func Shuffle(n int, swap func(i, j int)) { globalRand().Shuffle(n, swap) } + +// Read generates len(p) random bytes from the default Source and +// writes them into p. It always returns len(p) and a nil error. +// Read, unlike the Rand.Read method, is safe for concurrent use. +// +// Deprecated: For almost all use cases, crypto/rand.Read is more appropriate. +func Read(p []byte) (n int, err error) { return globalRand().Read(p) } + +// NormFloat64 returns a normally distributed float64 in the range +// [-math.MaxFloat64, +math.MaxFloat64] with +// standard normal distribution (mean = 0, stddev = 1) +// from the default Source. +// To produce a different normal distribution, callers can +// adjust the output using: +// +// sample = NormFloat64() * desiredStdDev + desiredMean +func NormFloat64() float64 { return globalRand().NormFloat64() } + +// ExpFloat64 returns an exponentially distributed float64 in the range +// (0, +math.MaxFloat64] with an exponential distribution whose rate parameter +// (lambda) is 1 and whose mean is 1/lambda (1) from the default Source. +// To produce a distribution with a different rate parameter, +// callers can adjust the output using: +// +// sample = ExpFloat64() / desiredRateParameter +func ExpFloat64() float64 { return globalRand().ExpFloat64() } + +type lockedSource struct { + lk sync.Mutex + s *rngSource +} + +func (r *lockedSource) Int63() (n int64) { + r.lk.Lock() + n = r.s.Int63() + r.lk.Unlock() + return +} + +func (r *lockedSource) Uint64() (n uint64) { + r.lk.Lock() + n = r.s.Uint64() + r.lk.Unlock() + return +} + +func (r *lockedSource) Seed(seed int64) { + r.lk.Lock() + r.seed(seed) + r.lk.Unlock() +} + +// seedPos implements Seed for a lockedSource without a race condition. +func (r *lockedSource) seedPos(seed int64, readPos *int8) { + r.lk.Lock() + r.seed(seed) + *readPos = 0 + r.lk.Unlock() +} + +// seed seeds the underlying source. +// The caller must have locked r.lk. +func (r *lockedSource) seed(seed int64) { + if r.s == nil { + r.s = newSource(seed) + } else { + r.s.Seed(seed) + } +} + +// read implements Read for a lockedSource without a race condition. +func (r *lockedSource) read(p []byte, readVal *int64, readPos *int8) (n int, err error) { + r.lk.Lock() + n, err = read(p, r.s, readVal, readPos) + r.lk.Unlock() + return +} diff --git a/internal/lib/math/rand/rng.go b/internal/lib/math/rand/rng.go new file mode 100644 index 00000000..1e4a9e01 --- /dev/null +++ b/internal/lib/math/rand/rng.go @@ -0,0 +1,252 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rand + +/* + * Uniform distribution + * + * algorithm by + * DP Mitchell and JA Reeds + */ + +const ( + rngLen = 607 + rngTap = 273 + rngMax = 1 << 63 + rngMask = rngMax - 1 + int32max = (1 << 31) - 1 +) + +var ( + // rngCooked used for seeding. See gen_cooked.go for details. + rngCooked [rngLen]int64 = [...]int64{ + -4181792142133755926, -4576982950128230565, 1395769623340756751, 5333664234075297259, + -6347679516498800754, 9033628115061424579, 7143218595135194537, 4812947590706362721, + 7937252194349799378, 5307299880338848416, 8209348851763925077, -7107630437535961764, + 4593015457530856296, 8140875735541888011, -5903942795589686782, -603556388664454774, + -7496297993371156308, 113108499721038619, 4569519971459345583, -4160538177779461077, + -6835753265595711384, -6507240692498089696, 6559392774825876886, 7650093201692370310, + 7684323884043752161, -8965504200858744418, -2629915517445760644, 271327514973697897, + -6433985589514657524, 1065192797246149621, 3344507881999356393, -4763574095074709175, + 7465081662728599889, 1014950805555097187, -4773931307508785033, -5742262670416273165, + 2418672789110888383, 5796562887576294778, 4484266064449540171, 3738982361971787048, + -4699774852342421385, 10530508058128498, -589538253572429690, -6598062107225984180, + 8660405965245884302, 10162832508971942, -2682657355892958417, 7031802312784620857, + 6240911277345944669, 831864355460801054, -1218937899312622917, 2116287251661052151, + 2202309800992166967, 9161020366945053561, 4069299552407763864, 4936383537992622449, + 457351505131524928, -8881176990926596454, -6375600354038175299, -7155351920868399290, + 4368649989588021065, 887231587095185257, -3659780529968199312, -2407146836602825512, + 5616972787034086048, -751562733459939242, 1686575021641186857, -5177887698780513806, + -4979215821652996885, -1375154703071198421, 5632136521049761902, -8390088894796940536, + -193645528485698615, -5979788902190688516, -4907000935050298721, -285522056888777828, + -2776431630044341707, 1679342092332374735, 6050638460742422078, -2229851317345194226, + -1582494184340482199, 5881353426285907985, 812786550756860885, 4541845584483343330, + -6497901820577766722, 4980675660146853729, -4012602956251539747, -329088717864244987, + -2896929232104691526, 1495812843684243920, -2153620458055647789, 7370257291860230865, + -2466442761497833547, 4706794511633873654, -1398851569026877145, 8549875090542453214, + -9189721207376179652, -7894453601103453165, 7297902601803624459, 1011190183918857495, + -6985347000036920864, 5147159997473910359, -8326859945294252826, 2659470849286379941, + 6097729358393448602, -7491646050550022124, -5117116194870963097, -896216826133240300, + -745860416168701406, 5803876044675762232, -787954255994554146, -3234519180203704564, + -4507534739750823898, -1657200065590290694, 505808562678895611, -4153273856159712438, + -8381261370078904295, 572156825025677802, 1791881013492340891, 3393267094866038768, + -5444650186382539299, 2352769483186201278, -7930912453007408350, -325464993179687389, + -3441562999710612272, -6489413242825283295, 5092019688680754699, -227247482082248967, + 4234737173186232084, 5027558287275472836, 4635198586344772304, -536033143587636457, + 5907508150730407386, -8438615781380831356, 972392927514829904, -3801314342046600696, + -4064951393885491917, -174840358296132583, 2407211146698877100, -1640089820333676239, + 3940796514530962282, -5882197405809569433, 3095313889586102949, -1818050141166537098, + 5832080132947175283, 7890064875145919662, 8184139210799583195, -8073512175445549678, + -7758774793014564506, -4581724029666783935, 3516491885471466898, -8267083515063118116, + 6657089965014657519, 5220884358887979358, 1796677326474620641, 5340761970648932916, + 1147977171614181568, 5066037465548252321, 2574765911837859848, 1085848279845204775, + -5873264506986385449, 6116438694366558490, 2107701075971293812, -7420077970933506541, + 2469478054175558874, -1855128755834809824, -5431463669011098282, -9038325065738319171, + -6966276280341336160, 7217693971077460129, -8314322083775271549, 7196649268545224266, + -3585711691453906209, -5267827091426810625, 8057528650917418961, -5084103596553648165, + -2601445448341207749, -7850010900052094367, 6527366231383600011, 3507654575162700890, + 9202058512774729859, 1954818376891585542, -2582991129724600103, 8299563319178235687, + -5321504681635821435, 7046310742295574065, -2376176645520785576, -7650733936335907755, + 8850422670118399721, 3631909142291992901, 5158881091950831288, -6340413719511654215, + 4763258931815816403, 6280052734341785344, -4979582628649810958, 2043464728020827976, + -2678071570832690343, 4562580375758598164, 5495451168795427352, -7485059175264624713, + 553004618757816492, 6895160632757959823, -989748114590090637, 7139506338801360852, + -672480814466784139, 5535668688139305547, 2430933853350256242, -3821430778991574732, + -1063731997747047009, -3065878205254005442, 7632066283658143750, 6308328381617103346, + 3681878764086140361, 3289686137190109749, 6587997200611086848, 244714774258135476, + -5143583659437639708, 8090302575944624335, 2945117363431356361, -8359047641006034763, + 3009039260312620700, -793344576772241777, 401084700045993341, -1968749590416080887, + 4707864159563588614, -3583123505891281857, -3240864324164777915, -5908273794572565703, + -3719524458082857382, -5281400669679581926, 8118566580304798074, 3839261274019871296, + 7062410411742090847, -8481991033874568140, 6027994129690250817, -6725542042704711878, + -2971981702428546974, -7854441788951256975, 8809096399316380241, 6492004350391900708, + 2462145737463489636, -8818543617934476634, -5070345602623085213, -8961586321599299868, + -3758656652254704451, -8630661632476012791, 6764129236657751224, -709716318315418359, + -3403028373052861600, -8838073512170985897, -3999237033416576341, -2920240395515973663, + -2073249475545404416, 368107899140673753, -6108185202296464250, -6307735683270494757, + 4782583894627718279, 6718292300699989587, 8387085186914375220, 3387513132024756289, + 4654329375432538231, -292704475491394206, -3848998599978456535, 7623042350483453954, + 7725442901813263321, 9186225467561587250, -5132344747257272453, -6865740430362196008, + 2530936820058611833, 1636551876240043639, -3658707362519810009, 1452244145334316253, + -7161729655835084979, -7943791770359481772, 9108481583171221009, -3200093350120725999, + 5007630032676973346, 2153168792952589781, 6720334534964750538, -3181825545719981703, + 3433922409283786309, 2285479922797300912, 3110614940896576130, -2856812446131932915, + -3804580617188639299, 7163298419643543757, 4891138053923696990, 580618510277907015, + 1684034065251686769, 4429514767357295841, -8893025458299325803, -8103734041042601133, + 7177515271653460134, 4589042248470800257, -1530083407795771245, 143607045258444228, + 246994305896273627, -8356954712051676521, 6473547110565816071, 3092379936208876896, + 2058427839513754051, -4089587328327907870, 8785882556301281247, -3074039370013608197, + -637529855400303673, 6137678347805511274, -7152924852417805802, 5708223427705576541, + -3223714144396531304, 4358391411789012426, 325123008708389849, 6837621693887290924, + 4843721905315627004, -3212720814705499393, -3825019837890901156, 4602025990114250980, + 1044646352569048800, 9106614159853161675, -8394115921626182539, -4304087667751778808, + 2681532557646850893, 3681559472488511871, -3915372517896561773, -2889241648411946534, + -6564663803938238204, -8060058171802589521, 581945337509520675, 3648778920718647903, + -4799698790548231394, -7602572252857820065, 220828013409515943, -1072987336855386047, + 4287360518296753003, -4633371852008891965, 5513660857261085186, -2258542936462001533, + -8744380348503999773, 8746140185685648781, 228500091334420247, 1356187007457302238, + 3019253992034194581, 3152601605678500003, -8793219284148773595, 5559581553696971176, + 4916432985369275664, -8559797105120221417, -5802598197927043732, 2868348622579915573, + -7224052902810357288, -5894682518218493085, 2587672709781371173, -7706116723325376475, + 3092343956317362483, -5561119517847711700, 972445599196498113, -1558506600978816441, + 1708913533482282562, -2305554874185907314, -6005743014309462908, -6653329009633068701, + -483583197311151195, 2488075924621352812, -4529369641467339140, -4663743555056261452, + 2997203966153298104, 1282559373026354493, 240113143146674385, 8665713329246516443, + 628141331766346752, -4651421219668005332, -7750560848702540400, 7596648026010355826, + -3132152619100351065, 7834161864828164065, 7103445518877254909, 4390861237357459201, + -4780718172614204074, -319889632007444440, 622261699494173647, -3186110786557562560, + -8718967088789066690, -1948156510637662747, -8212195255998774408, -7028621931231314745, + 2623071828615234808, -4066058308780939700, -5484966924888173764, -6683604512778046238, + -6756087640505506466, 5256026990536851868, 7841086888628396109, 6640857538655893162, + -8021284697816458310, -7109857044414059830, -1689021141511844405, -4298087301956291063, + -4077748265377282003, -998231156719803476, 2719520354384050532, 9132346697815513771, + 4332154495710163773, -2085582442760428892, 6994721091344268833, -2556143461985726874, + -8567931991128098309, 59934747298466858, -3098398008776739403, -265597256199410390, + 2332206071942466437, -7522315324568406181, 3154897383618636503, -7585605855467168281, + -6762850759087199275, 197309393502684135, -8579694182469508493, 2543179307861934850, + 4350769010207485119, -4468719947444108136, -7207776534213261296, -1224312577878317200, + 4287946071480840813, 8362686366770308971, 6486469209321732151, -5605644191012979782, + -1669018511020473564, 4450022655153542367, -7618176296641240059, -3896357471549267421, + -4596796223304447488, -6531150016257070659, -8982326463137525940, -4125325062227681798, + -1306489741394045544, -8338554946557245229, 5329160409530630596, 7790979528857726136, + 4955070238059373407, -4304834761432101506, -6215295852904371179, 3007769226071157901, + -6753025801236972788, 8928702772696731736, 7856187920214445904, -4748497451462800923, + 7900176660600710914, -7082800908938549136, -6797926979589575837, -6737316883512927978, + 4186670094382025798, 1883939007446035042, -414705992779907823, 3734134241178479257, + 4065968871360089196, 6953124200385847784, -7917685222115876751, -7585632937840318161, + -5567246375906782599, -5256612402221608788, 3106378204088556331, -2894472214076325998, + 4565385105440252958, 1979884289539493806, -6891578849933910383, 3783206694208922581, + 8464961209802336085, 2843963751609577687, 3030678195484896323, -4429654462759003204, + 4459239494808162889, 402587895800087237, 8057891408711167515, 4541888170938985079, + 1042662272908816815, -3666068979732206850, 2647678726283249984, 2144477441549833761, + -3417019821499388721, -2105601033380872185, 5916597177708541638, -8760774321402454447, + 8833658097025758785, 5970273481425315300, 563813119381731307, -6455022486202078793, + 1598828206250873866, -4016978389451217698, -2988328551145513985, -6071154634840136312, + 8469693267274066490, 125672920241807416, -3912292412830714870, -2559617104544284221, + -486523741806024092, -4735332261862713930, 5923302823487327109, -9082480245771672572, + -1808429243461201518, 7990420780896957397, 4317817392807076702, 3625184369705367340, + -6482649271566653105, -3480272027152017464, -3225473396345736649, -368878695502291645, + -3981164001421868007, -8522033136963788610, 7609280429197514109, 3020985755112334161, + -2572049329799262942, 2635195723621160615, 5144520864246028816, -8188285521126945980, + 1567242097116389047, 8172389260191636581, -2885551685425483535, -7060359469858316883, + -6480181133964513127, -7317004403633452381, 6011544915663598137, 5932255307352610768, + 2241128460406315459, -8327867140638080220, 3094483003111372717, 4583857460292963101, + 9079887171656594975, -384082854924064405, -3460631649611717935, 4225072055348026230, + -7385151438465742745, 3801620336801580414, -399845416774701952, -7446754431269675473, + 7899055018877642622, 5421679761463003041, 5521102963086275121, -4975092593295409910, + 8735487530905098534, -7462844945281082830, -2080886987197029914, -1000715163927557685, + -4253840471931071485, -5828896094657903328, 6424174453260338141, 359248545074932887, + -5949720754023045210, -2426265837057637212, 3030918217665093212, -9077771202237461772, + -3186796180789149575, 740416251634527158, -2142944401404840226, 6951781370868335478, + 399922722363687927, -8928469722407522623, -1378421100515597285, -8343051178220066766, + -3030716356046100229, -8811767350470065420, 9026808440365124461, 6440783557497587732, + 4615674634722404292, 539897290441580544, 2096238225866883852, 8751955639408182687, + -7316147128802486205, 7381039757301768559, 6157238513393239656, -1473377804940618233, + 8629571604380892756, 5280433031239081479, 7101611890139813254, 2479018537985767835, + 7169176924412769570, -1281305539061572506, -7865612307799218120, 2278447439451174845, + 3625338785743880657, 6477479539006708521, 8976185375579272206, -3712000482142939688, + 1326024180520890843, 7537449876596048829, 5464680203499696154, 3189671183162196045, + 6346751753565857109, -8982212049534145501, -6127578587196093755, -245039190118465649, + -6320577374581628592, 7208698530190629697, 7276901792339343736, -7490986807540332668, + 4133292154170828382, 2918308698224194548, -7703910638917631350, -3929437324238184044, + -4300543082831323144, -6344160503358350167, 5896236396443472108, -758328221503023383, + -1894351639983151068, -307900319840287220, -6278469401177312761, -2171292963361310674, + 8382142935188824023, 9103922860780351547, 4152330101494654406, + } +) + +type rngSource struct { + tap int // index into vec + feed int // index into vec + vec [rngLen]int64 // current feedback register +} + +// seed rng x[n+1] = 48271 * x[n] mod (2**31 - 1) +func seedrand(x int32) int32 { + const ( + A = 48271 + Q = 44488 + R = 3399 + ) + + hi := x / Q + lo := x % Q + x = A*lo - R*hi + if x < 0 { + x += int32max + } + return x +} + +// Seed uses the provided seed value to initialize the generator to a deterministic state. +func (rng *rngSource) Seed(seed int64) { + rng.tap = 0 + rng.feed = rngLen - rngTap + + seed = seed % int32max + if seed < 0 { + seed += int32max + } + if seed == 0 { + seed = 89482311 + } + + x := int32(seed) + for i := -20; i < rngLen; i++ { + x = seedrand(x) + if i >= 0 { + var u int64 + u = int64(x) << 40 + x = seedrand(x) + u ^= int64(x) << 20 + x = seedrand(x) + u ^= int64(x) + u ^= rngCooked[i] + rng.vec[i] = u + } + } +} + +// Int63 returns a non-negative pseudo-random 63-bit integer as an int64. +func (rng *rngSource) Int63() int64 { + return int64(rng.Uint64() & rngMask) +} + +// Uint64 returns a non-negative pseudo-random 64-bit integer as a uint64. +func (rng *rngSource) Uint64() uint64 { + rng.tap-- + if rng.tap < 0 { + rng.tap += rngLen + } + + rng.feed-- + if rng.feed < 0 { + rng.feed += rngLen + } + + x := rng.vec[rng.feed] + rng.vec[rng.tap] + rng.vec[rng.feed] = x + return uint64(x) +} diff --git a/internal/lib/math/rand/zipf.go b/internal/lib/math/rand/zipf.go new file mode 100644 index 00000000..f04c814e --- /dev/null +++ b/internal/lib/math/rand/zipf.go @@ -0,0 +1,77 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// W.Hormann, G.Derflinger: +// "Rejection-Inversion to Generate Variates +// from Monotone Discrete Distributions" +// http://eeyore.wu-wien.ac.at/papers/96-04-04.wh-der.ps.gz + +package rand + +import "math" + +// A Zipf generates Zipf distributed variates. +type Zipf struct { + r *Rand + imax float64 + v float64 + q float64 + s float64 + oneminusQ float64 + oneminusQinv float64 + hxm float64 + hx0minusHxm float64 +} + +func (z *Zipf) h(x float64) float64 { + return math.Exp(z.oneminusQ*math.Log(z.v+x)) * z.oneminusQinv +} + +func (z *Zipf) hinv(x float64) float64 { + return math.Exp(z.oneminusQinv*math.Log(z.oneminusQ*x)) - z.v +} + +// NewZipf returns a Zipf variate generator. +// The generator generates values k ∈ [0, imax] +// such that P(k) is proportional to (v + k) ** (-s). +// Requirements: s > 1 and v >= 1. +func NewZipf(r *Rand, s float64, v float64, imax uint64) *Zipf { + z := new(Zipf) + if s <= 1.0 || v < 1 { + return nil + } + z.r = r + z.imax = float64(imax) + z.v = v + z.q = s + z.oneminusQ = 1.0 - z.q + z.oneminusQinv = 1.0 / z.oneminusQ + z.hxm = z.h(z.imax + 0.5) + z.hx0minusHxm = z.h(0.5) - math.Exp(math.Log(z.v)*(-z.q)) - z.hxm + z.s = 1 - z.hinv(z.h(1.5)-math.Exp(-z.q*math.Log(z.v+1.0))) + return z +} + +// Uint64 returns a value drawn from the Zipf distribution described +// by the Zipf object. +func (z *Zipf) Uint64() uint64 { + if z == nil { + panic("rand: nil Zipf") + } + k := 0.0 + + for { + r := z.r.Float64() // r on [0,1] + ur := z.hxm + r*z.hx0minusHxm + x := z.hinv(ur) + k = math.Floor(x + 0.5) + if k-x <= z.s { + break + } + if ur >= z.h(k+0.5)-math.Exp(-math.Log(k+z.v)*z.q) { + break + } + } + return uint64(k) +} diff --git a/internal/lib/os/env.go b/internal/lib/os/env.go index 7c0f3174..550dac19 100644 --- a/internal/lib/os/env.go +++ b/internal/lib/os/env.go @@ -10,7 +10,6 @@ import ( "syscall" ) -/* TODO(xsw): // Expand replaces ${var} or $var in the string based on the mapping function. // For example, os.ExpandEnv(s) is equivalent to os.Expand(s, os.Getenv). func Expand(s string, mapping func(string) string) string { @@ -94,7 +93,6 @@ func getShellName(s string) (string, int) { } return s[:i], i } -*/ // Getenv retrieves the value of the environment variable named by the key. // It returns the value, which will be empty if the variable is not present. diff --git a/internal/lib/os/exec/exec.go b/internal/lib/os/exec/exec.go index c54ecc3e..8f9059dc 100644 --- a/internal/lib/os/exec/exec.go +++ b/internal/lib/os/exec/exec.go @@ -29,6 +29,8 @@ import ( "strings" "syscall" "time" + + "github.com/goplus/llgo/internal/lib/internal/syscall/execenv" ) // Error is returned by LookPath when it fails to classify a file as an @@ -1089,7 +1091,6 @@ func minInt(a, b int) int { // would be run as it is currently configured. If an error occurs in computing // the environment, it is returned alongside the best-effort copy. func (c *Cmd) environ() ([]string, error) { - /* TODO(xsw): var err error env := c.Env @@ -1128,8 +1129,6 @@ func (c *Cmd) environ() ([]string, error) { err = dedupErr } return addCriticalEnv(env), err - */ - panic("todo: exec.Cmd.environ") } // Environ returns a copy of the environment in which the command would be run diff --git a/internal/lib/os/exec_posix.go b/internal/lib/os/exec_posix.go index 1c698988..54462588 100644 --- a/internal/lib/os/exec_posix.go +++ b/internal/lib/os/exec_posix.go @@ -7,8 +7,10 @@ package os import ( + "runtime" "syscall" + "github.com/goplus/llgo/internal/lib/internal/itoa" "github.com/goplus/llgo/internal/lib/internal/syscall/execenv" ) @@ -96,7 +98,6 @@ func (p *ProcessState) sysUsage() any { } func (p *ProcessState) String() string { - /* TODO(xsw): if p == nil { return "" } @@ -124,8 +125,6 @@ func (p *ProcessState) String() string { res += " (core dumped)" } return res - */ - panic("todo: os.ProcessState.String") } // ExitCode returns the exit code of the exited process, or -1 diff --git a/internal/lib/os/file.go b/internal/lib/os/file.go index 5b309661..f0f87abc 100644 --- a/internal/lib/os/file.go +++ b/internal/lib/os/file.go @@ -467,6 +467,7 @@ func (dir dirFS) join(name string) (string, error) { } return string(dir) + string(PathSeparator) + name, nil } +*/ // ReadFile reads the named file and returns the contents. // A successful call returns err == nil, not err == EOF. @@ -529,4 +530,3 @@ func WriteFile(name string, data []byte, perm FileMode) error { } return err } -*/ diff --git a/internal/lib/os/file_posix.go b/internal/lib/os/file_posix.go index 03f1806c..684ff5be 100644 --- a/internal/lib/os/file_posix.go +++ b/internal/lib/os/file_posix.go @@ -16,13 +16,10 @@ import ( // be canceled and return immediately with an ErrClosed error. // Close will return an error if it has already been called. func (f *File) Close() error { - /* - if f == nil { - return ErrInvalid - } - return f.file.close() - */ - panic("todo: os.(*File).Close") + if f == nil { + return ErrInvalid + } + return f.close() } // pread reads len(b) bytes from the File starting at byte offset off. diff --git a/internal/lib/os/file_unix.go b/internal/lib/os/file_unix.go index 918890a0..fc75f0c1 100644 --- a/internal/lib/os/file_unix.go +++ b/internal/lib/os/file_unix.go @@ -250,6 +250,27 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) { return f, nil } +func (file *File) close() error { + return syscall.Close(int(file.fd)) + /* TODO(xsw): + if file.dirinfo != nil { + file.dirinfo.close() + file.dirinfo = nil + } + var err error + if e := file.pfd.Close(); e != nil { + if e == poll.ErrFileClosing { + e = ErrClosed + } + err = &PathError{Op: "close", Path: file.name, Err: e} + } + + // no need for a finalizer anymore + runtime.SetFinalizer(file, nil) + return err + */ +} + func tempDir() string { dir := Getenv("TMPDIR") if dir == "" { diff --git a/internal/lib/os/os.go b/internal/lib/os/os.go index 9350272c..591f2ced 100644 --- a/internal/lib/os/os.go +++ b/internal/lib/os/os.go @@ -274,9 +274,6 @@ func Mkdir(name string, perm FileMode) error { // func MkdirAll(path string, perm FileMode) error // func MkdirTemp(dir, pattern string) (string, error) // func NewSyscallError(syscall string, err error) error -func Pipe() (r *File, w *File, err error) { - panic("todo: os.Pipe") -} // func ReadFile(name string) ([]byte, error) diff --git a/internal/lib/os/pipe2_unix.go b/internal/lib/os/pipe2_unix.go new file mode 100644 index 00000000..2d293fdb --- /dev/null +++ b/internal/lib/os/pipe2_unix.go @@ -0,0 +1,22 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris + +package os + +import "syscall" + +// Pipe returns a connected pair of Files; reads from r return bytes written to w. +// It returns the files and an error, if any. +func Pipe() (r *File, w *File, err error) { + var p [2]int + + e := syscall.Pipe2(p[0:], syscall.O_CLOEXEC) + if e != nil { + return nil, nil, NewSyscallError("pipe2", e) + } + + return newFile(p[0], "|0", kindPipe), newFile(p[1], "|1", kindPipe), nil +} diff --git a/internal/lib/os/pipe_unix.go b/internal/lib/os/pipe_unix.go new file mode 100644 index 00000000..2eb11a04 --- /dev/null +++ b/internal/lib/os/pipe_unix.go @@ -0,0 +1,28 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build aix || darwin + +package os + +import "syscall" + +// Pipe returns a connected pair of Files; reads from r return bytes written to w. +// It returns the files and an error, if any. +func Pipe() (r *File, w *File, err error) { + var p [2]int + + // See ../syscall/exec.go for description of lock. + syscall.ForkLock.RLock() + e := syscall.Pipe(p[0:]) + if e != nil { + syscall.ForkLock.RUnlock() + return nil, nil, NewSyscallError("pipe", e) + } + syscall.CloseOnExec(p[0]) + syscall.CloseOnExec(p[1]) + syscall.ForkLock.RUnlock() + + return newFile(p[0], "|0", kindPipe), newFile(p[1], "|1", kindPipe), nil +} diff --git a/internal/lib/os/pipe_wasm.go b/internal/lib/os/pipe_wasm.go new file mode 100644 index 00000000..87a29b1f --- /dev/null +++ b/internal/lib/os/pipe_wasm.go @@ -0,0 +1,16 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build wasm + +package os + +import "syscall" + +// Pipe returns a connected pair of Files; reads from r return bytes written to w. +// It returns the files and an error, if any. +func Pipe() (r *File, w *File, err error) { + // Neither GOOS=js nor GOOS=wasip1 have pipes. + return nil, nil, NewSyscallError("pipe", syscall.ENOSYS) +} diff --git a/internal/lib/os/stat_darwin.go b/internal/lib/os/stat_darwin.go index 0725f6bb..2f5dc575 100644 --- a/internal/lib/os/stat_darwin.go +++ b/internal/lib/os/stat_darwin.go @@ -5,8 +5,9 @@ package os import ( - "syscall" "time" + + "github.com/goplus/llgo/c/syscall" ) func fillFileStatFromSys(fs *fileStat, name string) { diff --git a/internal/lib/os/stat_unix.go b/internal/lib/os/stat_unix.go index 04ae7304..c0840d31 100644 --- a/internal/lib/os/stat_unix.go +++ b/internal/lib/os/stat_unix.go @@ -6,12 +6,15 @@ package os -import "syscall" +import ( + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/os" + "github.com/goplus/llgo/internal/lib/syscall" +) // Stat returns the FileInfo structure describing file. // If there is an error, it will be of type *PathError. func (f *File) Stat() (FileInfo, error) { - /* TODO(xsw): if f == nil { return nil, ErrInvalid } @@ -22,8 +25,6 @@ func (f *File) Stat() (FileInfo, error) { } fillFileStatFromSys(&fs, f.name) return &fs, nil - */ - panic("todo: os.File.Stat") } // statNolog stats a file with no test logging. diff --git a/internal/lib/os/str.go b/internal/lib/os/str.go new file mode 100644 index 00000000..242c945c --- /dev/null +++ b/internal/lib/os/str.go @@ -0,0 +1,39 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Simple conversions to avoid depending on strconv. + +package os + +// itox converts val (an int) to a hexadecimal string. +func itox(val int) string { + if val < 0 { + return "-" + uitox(uint(-val)) + } + return uitox(uint(val)) +} + +const hex = "0123456789abcdef" + +// uitox converts val (a uint) to a hexadecimal string. +func uitox(val uint) string { + if val == 0 { // avoid string allocation + return "0x0" + } + var buf [20]byte // big enough for 64bit value base 16 + 0x + i := len(buf) - 1 + for val >= 16 { + q := val / 16 + buf[i] = hex[val%16] + i-- + val = q + } + // val < 16 + buf[i] = hex[val%16] + i-- + buf[i] = 'x' + i-- + buf[i] = '0' + return string(buf[i:]) +} diff --git a/internal/lib/os/types_unix.go b/internal/lib/os/types_unix.go index 1b90a5a1..602eee4c 100644 --- a/internal/lib/os/types_unix.go +++ b/internal/lib/os/types_unix.go @@ -7,8 +7,9 @@ package os import ( - "syscall" "time" + + "github.com/goplus/llgo/internal/lib/syscall" ) // A fileStat is the implementation of FileInfo returned by Stat and Lstat. diff --git a/internal/lib/os/wait_wait6.go b/internal/lib/os/wait_wait6.go new file mode 100644 index 00000000..994b8a60 --- /dev/null +++ b/internal/lib/os/wait_wait6.go @@ -0,0 +1,32 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build dragonfly || freebsd || netbsd + +package os + +import ( + "syscall" +) + +// blockUntilWaitable attempts to block until a call to p.Wait will +// succeed immediately, and reports whether it has done so. +// It does not actually call p.Wait. +func (p *Process) blockUntilWaitable() (bool, error) { + var errno syscall.Errno + for { + _, errno = wait6(_P_PID, p.Pid, syscall.WEXITED|syscall.WNOWAIT) + if errno != syscall.EINTR { + break + } + } + // TODO(xsw): + // runtime.KeepAlive(p) + if errno == syscall.ENOSYS { + return false, nil + } else if errno != 0 { + return false, NewSyscallError("wait6", errno) + } + return true, nil +} diff --git a/internal/lib/os/wait_waitid.go b/internal/lib/os/wait_waitid.go new file mode 100644 index 00000000..301254b3 --- /dev/null +++ b/internal/lib/os/wait_waitid.go @@ -0,0 +1,55 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// We used to used this code for Darwin, but according to issue #19314 +// waitid returns if the process is stopped, even when using WEXITED. + +//go:build linux + +package os + +import ( + "syscall" + _ "unsafe" + + "github.com/goplus/llgo/c" +) + +const _P_PID = 1 + +// int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options); +// +//go:linkname waitid C.waitid +func waitid(idtype, id uintptr, infop *uint64, options c.Int) c.Int + +// blockUntilWaitable attempts to block until a call to p.Wait will +// succeed immediately, and reports whether it has done so. +// It does not actually call p.Wait. +func (p *Process) blockUntilWaitable() (bool, error) { + // The waitid system call expects a pointer to a siginfo_t, + // which is 128 bytes on all Linux systems. + // On darwin/amd64, it requires 104 bytes. + // We don't care about the values it returns. + var siginfo [16]uint64 + psig := &siginfo[0] + var e syscall.Errno + for { + e = syscall.Errno(waitid(_P_PID, uintptr(p.Pid), psig, syscall.WEXITED|syscall.WNOWAIT)) + if e != syscall.EINTR { + break + } + } + // TODO(xsw): + // runtime.KeepAlive(p) + if e != 0 { + // waitid has been available since Linux 2.6.9, but + // reportedly is not available in Ubuntu on Windows. + // See issue 16610. + if e == syscall.ENOSYS { + return false, nil + } + return false, NewSyscallError("waitid", e) + } + return true, nil +} diff --git a/internal/lib/reflect/value.go b/internal/lib/reflect/value.go index c2155266..f2d8756f 100644 --- a/internal/lib/reflect/value.go +++ b/internal/lib/reflect/value.go @@ -1579,26 +1579,39 @@ func (v Value) CanUint() bool { // Uint returns v's underlying value, as a uint64. // It panics if v's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. func (v Value) Uint() uint64 { + f := v.flag k := v.kind() p := v.ptr - switch k { - case Uint: - return uint64(*(*uint)(p)) - case Uint8: - return uint64(*(*uint8)(p)) - case Uint16: - return uint64(*(*uint16)(p)) - case Uint32: - return uint64(*(*uint32)(p)) - case Uint64: - return *(*uint64)(p) - case Uintptr: - return uint64(*(*uintptr)(p)) + if f&flagAddr != 0 { + switch k { + case Uint: + return uint64(*(*uint)(p)) + case Uint8: + return uint64(*(*uint8)(p)) + case Uint16: + return uint64(*(*uint16)(p)) + case Uint32: + return uint64(*(*uint32)(p)) + case Uint64: + return *(*uint64)(p) + case Uintptr: + return uint64(*(*uintptr)(p)) + } + } else if unsafe.Sizeof(uintptr(0)) == 8 { + if k >= Uint && k <= Uintptr { + return uint64(uintptr(p)) + } + } else { + if k >= Uint && k <= Uint32 { + return uint64(uintptr(p)) + } + if k == Uint64 || k == Uintptr { + return *(*uint64)(p) + } } panic(&ValueError{"reflect.Value.Uint", v.kind()}) } -//go:nocheckptr // This prevents inlining Value.UnsafeAddr when -d=checkptr is enabled, // which ensures cmd/compile can recognize unsafe.Pointer(v.UnsafeAddr()) // and make an exception. diff --git a/internal/lib/sync/pool.go b/internal/lib/sync/pool.go index 98d5a11e..6d247ea9 100644 --- a/internal/lib/sync/pool.go +++ b/internal/lib/sync/pool.go @@ -24,6 +24,10 @@ type Pool struct { func (p *Pool) Put(x any) { } +// TODO(xsw): func (p *Pool) Get() any { - return p.New() + if p.New != nil { + return p.New() + } + return nil } diff --git a/internal/lib/sync/sync.go b/internal/lib/sync/sync.go index 415b6dec..69075e0c 100644 --- a/internal/lib/sync/sync.go +++ b/internal/lib/sync/sync.go @@ -98,11 +98,11 @@ type Once struct { func (o *Once) Do(f func()) { if !o.done { o.m.Lock() - defer o.m.Unlock() if !o.done { o.done = true f() } + o.m.Unlock() } } diff --git a/internal/lib/syscall/exec_libc.go b/internal/lib/syscall/exec_libc.go index 44557867..1c38a19a 100644 --- a/internal/lib/syscall/exec_libc.go +++ b/internal/lib/syscall/exec_libc.go @@ -11,6 +11,8 @@ package syscall import ( "runtime" "unsafe" + + "github.com/goplus/llgo/c" ) type SysProcAttr struct { @@ -77,8 +79,8 @@ func init() { // split the stack, or acquire mutexes). We can't call RawSyscall // because it's not safe even for BSD-subsystem calls. // -//go:norace -func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) { +// func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) { +func forkAndExecInChild(argv0 *c.Char, argv, envv **c.Char, chroot, dir *c.Char, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) { // Declare all variables at top in case any // declarations require heap allocation (e.g., err1). var ( diff --git a/internal/lib/syscall/exec_libc2.go b/internal/lib/syscall/exec_libc2.go index 61b2c3b3..3a5dede4 100644 --- a/internal/lib/syscall/exec_libc2.go +++ b/internal/lib/syscall/exec_libc2.go @@ -7,6 +7,8 @@ package syscall import ( + "unsafe" + "github.com/goplus/llgo/c" "github.com/goplus/llgo/c/os" "github.com/goplus/llgo/c/syscall" @@ -52,8 +54,6 @@ func runtime_AfterForkInChild() // For the same reason compiler does not race instrument it. // The calls to rawSyscall are okay because they are assembly // functions that do not grow the stack. -// -//go:norace func forkAndExecInChild(argv0 *c.Char, argv, envv **c.Char, chroot, dir *c.Char, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err1 Errno) { // Declare all variables at top in case any // declarations require heap allocation (e.g., err1). @@ -263,22 +263,17 @@ func forkAndExecInChild(argv0 *c.Char, argv, envv **c.Char, chroot, dir *c.Char, if fd[i] == i { // dup2(i, i) won't clear close-on-exec flag on Linux, // probably not elsewhere either. - ret := os.Fcntl(c.Int(fd[i]), syscall.F_SETFD, 0) - if ret != 0 { - err1 = Errno(ret) + if ret := os.Fcntl(c.Int(fd[i]), syscall.F_SETFD, 0); ret < 0 { + err1 = Errno(os.Errno) goto childerror } continue } - /* TODO(xsw): // The new fd is created NOT close-on-exec, - // which is exactly what we want. - _, _, err1 = rawSyscall(abi.FuncPCABI0(libc_dup2_trampoline), uintptr(fd[i]), uintptr(i), 0) - if err1 != 0 { + if ret := os.Dup2(c.Int(fd[i]), c.Int(i)); ret < 0 { + err1 = Errno(os.Errno) goto childerror } - */ - panic("todo: syscall.forkAndExecInChild - dup2") } // By convention, we don't close-on-exec the fds we are @@ -286,10 +281,7 @@ func forkAndExecInChild(argv0 *c.Char, argv, envv **c.Char, chroot, dir *c.Char, // Programs that know they inherit fds >= 3 will need // to set them close-on-exec. for i = len(fd); i < 3; i++ { - /* TODO(xsw): - rawSyscall(abi.FuncPCABI0(libc_close_trampoline), uintptr(i), 0, 0) - */ - panic("todo: syscall.forkAndExecInChild - for i") + os.Close(c.Int(i)) } // Detach fd 0 from tty @@ -329,12 +321,9 @@ func forkAndExecInChild(argv0 *c.Char, argv, envv **c.Char, chroot, dir *c.Char, */ childerror: - /* TODO(xsw): // send error code on pipe - rawSyscall(abi.FuncPCABI0(libc_write_trampoline), uintptr(pipe), uintptr(unsafe.Pointer(&err1)), unsafe.Sizeof(err1)) + os.Write(c.Int(pipe), unsafe.Pointer(&err1), unsafe.Sizeof(err1)) for { - rawSyscall(abi.FuncPCABI0(libc_exit_trampoline), 253, 0, 0) + os.Exit(253) } - */ - panic("todo: syscall.forkAndExecInChild - childerror") } diff --git a/internal/lib/syscall/exec_linux.go b/internal/lib/syscall/exec_linux.go index c66e820f..fb93fce3 100644 --- a/internal/lib/syscall/exec_linux.go +++ b/internal/lib/syscall/exec_linux.go @@ -6,6 +6,8 @@ package syscall +import "github.com/goplus/llgo/c" + // Linux unshare/clone/clone2/clone3 flags, architecture-independent, // copied from linux/sched.h. const ( @@ -119,8 +121,8 @@ func runtime_AfterForkInChild() // The calls to RawSyscall are okay because they are assembly // functions that do not grow the stack. // -//go:norace -func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) { +// func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) { +func forkAndExecInChild(argv0 *c.Char, argv, envv **c.Char, chroot, dir *c.Char, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) { /* TODO(xsw): // Set up and fork. This returns immediately in the parent or // if there's an error. diff --git a/internal/lib/syscall/exec_unix.go b/internal/lib/syscall/exec_unix.go index d979d891..21a12178 100644 --- a/internal/lib/syscall/exec_unix.go +++ b/internal/lib/syscall/exec_unix.go @@ -68,10 +68,7 @@ import ( var ForkLock sync.RWMutex func CloseOnExec(fd int) { - /* TODO(xsw): - fcntl(fd, F_SETFD, FD_CLOEXEC) - */ - panic("todo: syscall.CloseOnExec") + os.Fcntl(c.Int(fd), syscall.F_SETFD, syscall.FD_CLOEXEC) } func SetNonblock(fd int, nonblocking bool) (err error) { diff --git a/internal/lib/syscall/forkpipe2.go b/internal/lib/syscall/forkpipe2.go new file mode 100644 index 00000000..b5cd1d30 --- /dev/null +++ b/internal/lib/syscall/forkpipe2.go @@ -0,0 +1,105 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris + +package syscall + +import ( + "sync" + + "github.com/goplus/llgo/c/syscall" +) + +// forkExecPipe atomically opens a pipe with O_CLOEXEC set on both file +// descriptors. +func forkExecPipe(p []int) error { + return Pipe2(p, syscall.O_CLOEXEC) +} + +var ( + // Guard the forking variable. + forkingLock sync.Mutex + // Number of goroutines currently forking, and thus the + // number of goroutines holding a conceptual write lock + // on ForkLock. + forking int +) + +// TODO(xsw): +// hasWaitingReaders reports whether any goroutine is waiting +// to acquire a read lock on rw. It is defined in the sync package. +func hasWaitingReaders(rw *sync.RWMutex) bool { + panic("todo: syscall.hasWaitingReaders in sync package") +} + +// acquireForkLock acquires a write lock on ForkLock. +// ForkLock is exported and we've promised that during a fork +// we will call ForkLock.Lock, so that no other threads create +// new fds that are not yet close-on-exec before we fork. +// But that forces all fork calls to be serialized, which is bad. +// But we haven't promised that serialization, and it is essentially +// undetectable by other users of ForkLock, which is good. +// Avoid the serialization by ensuring that ForkLock is locked +// at the first fork and unlocked when there are no more forks. +func acquireForkLock() { + forkingLock.Lock() + defer forkingLock.Unlock() + + if forking == 0 { + // There is no current write lock on ForkLock. + ForkLock.Lock() + forking++ + return + } + + // ForkLock is currently locked for writing. + + if hasWaitingReaders(&ForkLock) { + // ForkLock is locked for writing, and at least one + // goroutine is waiting to read from it. + // To avoid lock starvation, allow readers to proceed. + // The simple way to do this is for us to acquire a + // read lock. That will block us until all current + // conceptual write locks are released. + // + // Note that this case is unusual on modern systems + // with O_CLOEXEC and SOCK_CLOEXEC. On those systems + // the standard library should never take a read + // lock on ForkLock. + + forkingLock.Unlock() + + ForkLock.RLock() + ForkLock.RUnlock() + + forkingLock.Lock() + + // Readers got a chance, so now take the write lock. + + if forking == 0 { + ForkLock.Lock() + } + } + + forking++ +} + +// releaseForkLock releases the conceptual write lock on ForkLock +// acquired by acquireForkLock. +func releaseForkLock() { + forkingLock.Lock() + defer forkingLock.Unlock() + + if forking <= 0 { + panic("syscall.releaseForkLock: negative count") + } + + forking-- + + if forking == 0 { + // No more conceptual write locks. + ForkLock.Unlock() + } +} diff --git a/internal/lib/syscall/syscall_linux.go b/internal/lib/syscall/syscall_linux.go index a3e05ff2..17d9fe95 100644 --- a/internal/lib/syscall/syscall_linux.go +++ b/internal/lib/syscall/syscall_linux.go @@ -11,6 +11,108 @@ package syscall +import ( + _ "unsafe" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/syscall" +) + +// ----------------------------------------------------------------------------- + +type WaitStatus uint32 + +// Wait status is 7 bits at bottom, either 0 (exited), +// 0x7F (stopped), or a signal number that caused an exit. +// The 0x80 bit is whether there was a core dump. +// An extra number (exit code, signal causing a stop) +// is in the high bits. At least that's the idea. +// There are various irregularities. For example, the +// "continued" status is 0xFFFF, distinguishing itself +// from stopped via the core dump bit. + +const ( + mask = 0x7F + core = 0x80 + exited = 0x00 + stopped = 0x7F + shift = 8 +) + +func (w WaitStatus) Exited() bool { return w&mask == exited } + +func (w WaitStatus) Signaled() bool { return w&mask != stopped && w&mask != exited } + +func (w WaitStatus) Stopped() bool { return w&0xFF == stopped } + +func (w WaitStatus) Continued() bool { return w == 0xFFFF } + +func (w WaitStatus) CoreDump() bool { return w.Signaled() && w&core != 0 } + +func (w WaitStatus) ExitStatus() int { + if !w.Exited() { + return -1 + } + return int(w>>shift) & 0xFF +} + +func (w WaitStatus) Signal() Signal { + if !w.Signaled() { + return -1 + } + return Signal(w & mask) +} + +func (w WaitStatus) StopSignal() Signal { + if !w.Stopped() { + return -1 + } + return Signal(w>>shift) & 0xFF +} + +/* TODO(xsw): +func (w WaitStatus) TrapCause() int { + if w.StopSignal() != SIGTRAP { + return -1 + } + return int(w>>shift) >> 8 +} +*/ + +func Wait4(pid int, wstatus *WaitStatus, options int, rusage *syscall.Rusage) (wpid int, err error) { + var status c.Int + wpid, err = wait4(pid, &status, options, rusage) + if wstatus != nil { + *wstatus = WaitStatus(status) + } + return +} + +// ----------------------------------------------------------------------------- + +// int pipe2(int pipefd[2], int flags); +// +//go:linkname pipe2 C.pipe2 +func pipe2(pipefd *[2]c.Int, flags c.Int) c.Int + +func Pipe2(p []int, flags int) error { + if len(p) != 2 { + return Errno(syscall.EINVAL) + } + var pp [2]c.Int + ret := pipe2(&pp, c.Int(flags)) + if ret == 0 { + p[0] = int(pp[0]) + p[1] = int(pp[1]) + return nil + } + return Errno(ret) +} + +// ----------------------------------------------------------------------------- + func Faccessat(dirfd int, path string, mode uint32, flags int) (err error) { panic("todo: syscall.Faccessat") } + +// ----------------------------------------------------------------------------- diff --git a/internal/runtime/z_chan.go b/internal/runtime/z_chan.go index 950c6096..fca39e59 100644 --- a/internal/runtime/z_chan.go +++ b/internal/runtime/z_chan.go @@ -239,6 +239,12 @@ func (p *selectOp) init() { p.sem = false } +func (p *selectOp) end() { + p.mutex.Destroy() + p.cond.Destroy() + p.next = nil +} + func (p *selectOp) notify() { p.mutex.Lock() p.sem = true @@ -299,6 +305,7 @@ func Select(ops ...ChanOp) (isel int, recvOK bool) { for _, op := range ops { endSelect(op.C, selOp) } + selOp.end() return } @@ -317,7 +324,6 @@ func endSelect(c *Chan, selOp *selectOp) { } *pp = selOp.next c.mutex.Unlock() - selOp.next = nil } // ----------------------------------------------------------------------------- diff --git a/internal/runtime/z_error.go b/internal/runtime/z_error.go index eb05f07c..69cdb953 100644 --- a/internal/runtime/z_error.go +++ b/internal/runtime/z_error.go @@ -96,6 +96,10 @@ func printany(i any) { print(v) case string: print(v) + case error: + print(v.Error()) + case interface{ String() string }: + print(v.String()) default: printanycustomtype(i) } diff --git a/internal/runtime/z_rt.go b/internal/runtime/z_rt.go index c100ade5..c11ebb6c 100644 --- a/internal/runtime/z_rt.go +++ b/internal/runtime/z_rt.go @@ -30,6 +30,7 @@ type Defer struct { Addr unsafe.Pointer // sigjmpbuf Bits uintptr Link *Defer + Reth unsafe.Pointer // block address after Rethrow Rund unsafe.Pointer // block address after RunDefers } @@ -46,12 +47,6 @@ func Recover() (ret any) { // Panic panics with a value. func Panic(v any) { - switch e := v.(type) { - case error: - v = e.Error() - case interface{ String() string }: - v = e.String() - } ptr := c.Malloc(unsafe.Sizeof(v)) *(*any)(ptr) = v excepKey.Set(ptr) @@ -82,10 +77,6 @@ func init() { // ----------------------------------------------------------------------------- -func unpackEface(i any) *eface { - return (*eface)(unsafe.Pointer(&i)) -} - // TracePanic prints panic message. func TracePanic(v any) { print("panic: ") diff --git a/py/dict.go b/py/dict.go index 8f8923b4..817d3ee8 100644 --- a/py/dict.go +++ b/py/dict.go @@ -42,4 +42,22 @@ func (d *Object) DictValues() *Object { return nil } // llgo:link (*Object).DictItems C.PyDict_Items func (d *Object) DictItems() *Object { return nil } +// Insert val into the dictionary d with a key of key. key must be hashable; +// if it isn’t, return -1 and TypeError will be set. Return 0 on success or +// -1 on failure. +// +// llgo:link (*Object).DictSetItem C.PyDict_SetItem +func (d *Object) DictSetItem(key *Object, val *Object) *Object { return nil } + +// Return the object from dictionary d which has a key key. Return nil if the +// key key is not present, but without setting an exception. +// +// llgo:link (*Object).DictGetItem C.PyDict_GetItem +func (d *Object) DictGetItem(key *Object) *Object { return nil } + +// Return the number of items in the dictionary. +// +// llgo:link (*Object).DictSize C.PyDict_Size +func (d *Object) DictSize() uintptr { return 0 } + // ----------------------------------------------------------------------------- diff --git a/rust/.gitkeep b/rust/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/ssa/datastruct.go b/ssa/datastruct.go index 6157d57d..503f5682 100644 --- a/ssa/datastruct.go +++ b/ssa/datastruct.go @@ -579,8 +579,8 @@ func (b Builder) Next(typ Type, iter Expr, isString bool) Expr { return b.InlineCall(b.Pkg.rtFunc("StringIterNext"), iter) } prog := b.Prog - ktyp := prog.Type(typ.raw.Type.(*types.Map).Key(), InGo) - vtyp := prog.Type(typ.raw.Type.(*types.Map).Elem(), InGo) + ktyp := prog.Type(typ.raw.Type.Underlying().(*types.Map).Key(), InGo) + vtyp := prog.Type(typ.raw.Type.Underlying().(*types.Map).Elem(), InGo) rets := b.InlineCall(b.Pkg.rtFunc("MapIterNext"), iter) ok := b.impl.CreateExtractValue(rets.impl, 0, "") t := prog.Struct(prog.Bool(), ktyp, vtyp) diff --git a/ssa/eh.go b/ssa/eh.go index 4aff254c..7ddbaaff 100644 --- a/ssa/eh.go +++ b/ssa/eh.go @@ -63,7 +63,11 @@ func (b Builder) AllocaSigjmpBuf() Expr { } func (b Builder) Sigsetjmp(jb, savemask Expr) Expr { - fn := b.Pkg.cFunc("sigsetjmp", b.Prog.tySigsetjmp()) + fname := "sigsetjmp" + if b.Prog.target.GOOS == "linux" { + fname = "__sigsetjmp" + } + fn := b.Pkg.cFunc(fname, b.Prog.tySigsetjmp()) return b.Call(fn, jb, savemask) } @@ -86,14 +90,16 @@ func (p Function) deferInitBuilder() (b Builder, next BasicBlock) { } type aDefer struct { - nextBit int // next defer bit - key Expr // pthread TLS key - data Expr // pointer to runtime.Defer - bitsPtr Expr // pointer to defer bits - rundPtr Expr // pointer to RunDefers index - procBlk BasicBlock // deferProc block - runsNext []BasicBlock // next blocks of RunDefers - stmts []func(bits Expr) + nextBit int // next defer bit + key Expr // pthread TLS key + data Expr // pointer to runtime.Defer + bitsPtr Expr // pointer to defer bits + rethPtr Expr // next block of Rethrow + rundPtr Expr // next block of RunDefers + procBlk BasicBlock // deferProc block + panicBlk BasicBlock // panic block (runDefers and rethrow) + rundsNext []BasicBlock // next blocks of RunDefers + stmts []func(bits Expr) } func (p Package) keyInit(name string) { @@ -124,53 +130,59 @@ const ( // 0: addr sigjmpbuf // 1: bits uintptr // 2: link *Defer - // 3: rund voidptr + // 3: reth voidptr: block address after Rethrow + // 4: rund voidptr: block address after RunDefers deferSigjmpbuf = iota deferBits deferLink - deferRund + deferRethrow + deferRunDefers ) func (b Builder) getDefer(kind DoAction) *aDefer { self := b.Func if self.defer_ == nil { // TODO(xsw): check if in pkg.init - var next, rundBlk BasicBlock + var next, panicBlk BasicBlock if kind != DeferAlways { b, next = self.deferInitBuilder() } + prog := b.Prog + blks := self.MakeBlocks(2) + procBlk, rethrowBlk := blks[0], blks[1] + key := b.deferKey() zero := prog.Val(uintptr(0)) link := Expr{b.pthreadGetspecific(key).impl, prog.DeferPtr()} jb := b.AllocaSigjmpBuf() - ptr := b.aggregateAlloca(prog.Defer(), jb.impl, zero.impl, link.impl) + ptr := b.aggregateAlloca(prog.Defer(), jb.impl, zero.impl, link.impl, procBlk.Addr().impl) deferData := Expr{ptr, prog.DeferPtr()} b.pthreadSetspecific(key, deferData) - blks := self.MakeBlocks(2) - procBlk, rethrowBlk := blks[0], blks[1] bitsPtr := b.FieldAddr(deferData, deferBits) - rundPtr := b.FieldAddr(deferData, deferRund) - self.defer_ = &aDefer{ - key: key, - data: deferData, - bitsPtr: bitsPtr, - rundPtr: rundPtr, - procBlk: procBlk, - runsNext: []BasicBlock{rethrowBlk}, - } + rethPtr := b.FieldAddr(deferData, deferRethrow) + rundPtr := b.FieldAddr(deferData, deferRunDefers) + czero := prog.IntVal(0, prog.CInt()) retval := b.Sigsetjmp(jb, czero) if kind != DeferAlways { - rundBlk = self.MakeBlock("") + panicBlk = self.MakeBlock("") } else { blks = self.MakeBlocks(2) - next, rundBlk = blks[0], blks[1] + next, panicBlk = blks[0], blks[1] + } + b.If(b.BinOp(token.EQL, retval, czero), next, panicBlk) + + self.defer_ = &aDefer{ + key: key, + data: deferData, + bitsPtr: bitsPtr, + rethPtr: rethPtr, + rundPtr: rundPtr, + procBlk: procBlk, + panicBlk: panicBlk, + rundsNext: []BasicBlock{rethrowBlk}, } - b.If(b.BinOp(token.EQL, retval, czero), next, rundBlk) - b.SetBlockEx(rundBlk, AtEnd, false) // exec runDefers and rethrow - b.Store(rundPtr, rethrowBlk.Addr()) - b.Jump(procBlk) b.SetBlockEx(rethrowBlk, AtEnd, false) // rethrow b.Call(b.Pkg.rtFunc("Rethrow"), link) @@ -209,7 +221,7 @@ func (b Builder) Defer(kind DoAction, fn Expr, args ...Expr) { case DeferAlways: // nothing to do default: - panic("todo: DeferInLoop is not supported") + panic("todo: DeferInLoop is not supported - " + b.Func.Name()) } self.stmts = append(self.stmts, func(bits Expr) { switch kind { @@ -229,7 +241,7 @@ func (b Builder) Defer(kind DoAction, fn Expr, args ...Expr) { func (b Builder) RunDefers() { self := b.getDefer(DeferInCond) blk := b.Func.MakeBlock("") - self.runsNext = append(self.runsNext, blk) + self.rundsNext = append(self.rundsNext, blk) b.Store(self.rundPtr, blk.Addr()) b.Jump(self.procBlk) @@ -243,20 +255,42 @@ func (p Function) endDefer(b Builder) { if self == nil { return } - nexts := self.runsNext + nexts := self.rundsNext if len(nexts) == 0 { return } - b.SetBlockEx(self.procBlk, AtEnd, true) - bits := b.Load(self.bitsPtr) - stmts := self.stmts - for i := len(stmts) - 1; i >= 0; i-- { - stmts[i](bits) - } + rethrowBlk := nexts[0] + procBlk := self.procBlk + panicBlk := self.panicBlk + rethPtr := self.rethPtr + rundPtr := self.rundPtr + bitsPtr := self.bitsPtr + + stmts := self.stmts + n := len(stmts) + rethsNext := make([]BasicBlock, n+1) + blks := p.MakeBlocks(n - 1) + copy(rethsNext[1:], blks) + rethsNext[0] = rethrowBlk + rethsNext[n] = procBlk + + for i := n - 1; i >= 0; i-- { + rethNext := rethsNext[i] + b.SetBlockEx(rethsNext[i+1], AtEnd, true) + b.Store(rethPtr, rethNext.Addr()) + stmts[i](b.Load(bitsPtr)) + if i != 0 { + b.Jump(rethNext) + } + } link := b.getField(b.Load(self.data), deferLink) b.pthreadSetspecific(self.key, link) - b.IndirectJump(b.Load(self.rundPtr), nexts) + b.IndirectJump(b.Load(rundPtr), nexts) + + b.SetBlockEx(panicBlk, AtEnd, false) // panicBlk: exec runDefers and rethrow + b.Store(rundPtr, rethrowBlk.Addr()) + b.IndirectJump(b.Load(rethPtr), rethsNext) } // ----------------------------------------------------------------------------- diff --git a/ssa/expr.go b/ssa/expr.go index 95261611..7bbb8293 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -388,7 +388,7 @@ var boolPredOpToLLVM = []llvm.IntPredicate{ token.NEQ - predOpBase: llvm.IntNE, } -// EQL NEQ LSS LEQ GTR GEQ == != < <= < >= +// EQL NEQ LSS LEQ GTR GEQ == != < <= > >= func isPredOp(op token.Token) bool { return op >= predOpBase && op <= predOpLast } @@ -397,7 +397,7 @@ func isPredOp(op token.Token) bool { // op can be: // ADD SUB MUL QUO REM + - * / % // AND OR XOR SHL SHR AND_NOT & | ^ << >> &^ -// EQL NEQ LSS LEQ GTR GEQ == != < <= < >= +// EQL NEQ LSS LEQ GTR GEQ == != < <= > >= func (b Builder) BinOp(op token.Token, x, y Expr) Expr { if debugInstr { log.Printf("BinOp %d, %v, %v\n", op, x.impl, y.impl) @@ -495,7 +495,7 @@ func (b Builder) BinOp(op token.Token, x, y Expr) Expr { llop := logicOpToLLVM[op-logicOpBase] return Expr{llvm.CreateBinOp(b.impl, llop, x.impl, y.impl), x.Type} } - case isPredOp(op): // op: == != < <= < >= + case isPredOp(op): // op: == != < <= > >= prog := b.Prog tret := prog.Bool() kind := x.kind @@ -567,7 +567,7 @@ func (b Builder) BinOp(op token.Token, x, y Expr) Expr { return Expr{llvm.CreateICmp(b.impl, pred, x.impl, y.impl), tret} } case vkArray: - typ := x.raw.Type.(*types.Array) + typ := x.raw.Type.Underlying().(*types.Array) elem := b.Prog.Elem(x.Type) ret := prog.BoolVal(true) for i, n := 0, int(typ.Len()); i < n; i++ { @@ -699,8 +699,31 @@ func (b Builder) ChangeType(t Type, x Expr) (ret Expr) { if debugInstr { log.Printf("ChangeType %v, %v\n", t.RawType(), x.impl) } - if t.kind == vkClosure && x.kind == vkFuncDecl { - ret.impl = checkExpr(x, t.raw.Type.Underlying(), b).impl + if t.kind == vkClosure { + switch x.kind { + case vkFuncDecl: + ret.impl = checkExpr(x, t.raw.Type, b).impl + case vkClosure: + // TODO(xsw): change type should be a noop instruction + convType := func() Expr { + r := Expr{llvm.CreateAlloca(b.impl, t.ll), b.Prog.Pointer(t)} + b.Store(r, x) + return b.Load(r) + } + switch t.RawType().(type) { + case *types.Named: + if _, ok := x.RawType().(*types.Struct); ok { + return convType() + } + case *types.Struct: + if _, ok := x.RawType().(*types.Named); ok { + return convType() + } + } + fallthrough + default: + ret.impl = x.impl + } } else { ret.impl = x.impl } @@ -996,6 +1019,22 @@ func (b Builder) Do(da DoAction, fn Expr, args ...Expr) (ret Expr) { return } +// compareSelect performs a series of comparisons and selections based on the +// given comparison op. It's used to implement operations like min and max. +// +// The function iterates through the provided expressions, comparing each with +// the current result using the specified comparison op. It selects the +// appropriate value based on the comparison. +func (b Builder) compareSelect(op token.Token, x Expr, y ...Expr) Expr { + ret := x + for _, v := range y { + cond := b.BinOp(op, ret, v) + sel := llvm.CreateSelect(b.impl, cond.impl, ret.impl, v.impl) + ret = Expr{sel, ret.Type} + } + return ret +} + // A Builtin represents a specific use of a built-in function, e.g. len. // // Builtins are immutable values. Builtins do not have addresses. @@ -1106,6 +1145,14 @@ func (b Builder) BuiltinCall(fn string, args ...Expr) (ret Expr) { b.Call(b.Pkg.rtFunc("MapClear"), t, m) return } + case "min": + if len(args) > 0 { + return b.compareSelect(token.LSS, args[0], args[1:]...) + } + case "max": + if len(args) > 0 { + return b.compareSelect(token.GTR, args[0], args[1:]...) + } } panic("todo: " + fn) } @@ -1177,7 +1224,7 @@ func (b Builder) PrintEx(ln bool, args ...Expr) (ret Expr) { // ----------------------------------------------------------------------------- func checkExpr(v Expr, t types.Type, b Builder) Expr { - if t, ok := t.(*types.Struct); ok && isClosure(t) { + if st, ok := t.Underlying().(*types.Struct); ok && isClosure(st) { if v.kind != vkClosure { return b.Pkg.closureStub(b, t, v) } diff --git a/ssa/package.go b/ssa/package.go index 9ce5f227..df23a4bb 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -19,6 +19,7 @@ package ssa import ( "go/token" "go/types" + "runtime" "strconv" "unsafe" @@ -240,7 +241,10 @@ type Program = *aProgram // NewProgram creates a new program. func NewProgram(target *Target) Program { if target == nil { - target = &Target{} + target = &Target{ + GOOS: runtime.GOOS, + GOARCH: runtime.GOARCH, + } } ctx := llvm.NewContext() td := target.targetData() // TODO(xsw): target config @@ -653,7 +657,7 @@ const ( closureStub = "__llgo_stub." ) -func (p Package) closureStub(b Builder, t *types.Struct, v Expr) Expr { +func (p Package) closureStub(b Builder, t types.Type, v Expr) Expr { name := v.impl.Name() prog := b.Prog nilVal := prog.Nil(prog.VoidPtr()).impl diff --git a/ssa/ssa_test.go b/ssa/ssa_test.go index 81cd739f..4946869d 100644 --- a/ssa/ssa_test.go +++ b/ssa/ssa_test.go @@ -521,3 +521,34 @@ func TestBasicType(t *testing.T) { } } } + +func TestCompareSelect(t *testing.T) { + prog := NewProgram(nil) + pkg := prog.NewPackage("bar", "foo/bar") + + params := types.NewTuple( + types.NewVar(0, nil, "a", types.Typ[types.Int]), + types.NewVar(0, nil, "b", types.Typ[types.Int]), + types.NewVar(0, nil, "c", types.Typ[types.Int]), + ) + rets := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int])) + sig := types.NewSignatureType(nil, nil, nil, params, rets, false) + fn := pkg.NewFunc("fn", sig, InGo) + + b := fn.MakeBody(1) + result := b.compareSelect(token.GTR, fn.Param(0), fn.Param(1), fn.Param(2)) + b.Return(result) + + assertPkg(t, pkg, `; ModuleID = 'foo/bar' +source_filename = "foo/bar" + +define i64 @fn(i64 %0, i64 %1, i64 %2) { +_llgo_0: + %3 = icmp sgt i64 %0, %1 + %4 = select i1 %3, i64 %0, i64 %1 + %5 = icmp sgt i64 %4, %2 + %6 = select i1 %5, i64 %4, i64 %2 + ret i64 %6 +} +`) +} diff --git a/xtool/env/build.go b/x/env/build.go similarity index 100% rename from xtool/env/build.go rename to x/env/build.go diff --git a/xtool/env/version.go b/x/env/version.go similarity index 100% rename from xtool/env/version.go rename to x/env/version.go diff --git a/xtool/env/env.go b/xtool/env/env.go index ded23cff..4285deb3 100644 --- a/xtool/env/env.go +++ b/xtool/env/env.go @@ -35,12 +35,9 @@ func ExpandEnv(s string) string { func expandEnvWithCmd(s string) string { expanded := reSubcmd.ReplaceAllStringFunc(s, func(m string) string { - subcmd := strings.TrimSpace(s[2 : len(s)-1]) - + subcmd := strings.TrimSpace(m[2 : len(m)-1]) args := parseSubcmd(subcmd) - cmd := args[0] - if cmd != "pkg-config" && cmd != "llvm-config" { fmt.Fprintf(os.Stderr, "expand cmd only support pkg-config and llvm-config: '%s'\n", subcmd) return "" @@ -55,9 +52,9 @@ func expandEnvWithCmd(s string) string { return "" } - return string(out) + return strings.Replace(strings.TrimSpace(string(out)), "\n", " ", -1) }) - return os.Expand(expanded, os.Getenv) + return strings.TrimSpace(os.Expand(expanded, os.Getenv)) } func parseSubcmd(s string) []string { diff --git a/xtool/nm/index.go b/xtool/nm/nmindex/index.go similarity index 90% rename from xtool/nm/index.go rename to xtool/nm/nmindex/index.go index 912f5806..5f325444 100644 --- a/xtool/nm/index.go +++ b/xtool/nm/nmindex/index.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package nm +package nmindex import ( "bytes" @@ -24,13 +24,15 @@ import ( "os" "path/filepath" "strings" + + "github.com/goplus/llgo/xtool/nm" ) type IndexBuilder struct { - nm *Cmd + nm *nm.Cmd } -func NewIndexBuilder(nm *Cmd) *IndexBuilder { +func NewIndexBuilder(nm *nm.Cmd) *IndexBuilder { return &IndexBuilder{nm} } @@ -95,12 +97,12 @@ func (p *IndexBuilder) IndexFile(arFile, outFile string) (err error) { } for _, sym := range item.Symbols { switch sym.Type { - case Text, Data, BSS, Rodata, 'S', 'C', 'W', 'A': + case nm.Text, nm.Data, nm.BSS, nm.Rodata, 'S', 'C', 'W', 'A': b.WriteByte(byte(sym.Type)) b.WriteByte(' ') b.WriteString(sym.Name) b.WriteByte('\n') - case Undefined, LocalText, LocalData, LocalBSS, LocalASym, 'I', 'i', 'a', 'w': + case nm.Undefined, nm.LocalText, nm.LocalData, nm.LocalBSS, nm.LocalASym, 'I', 'i', 'a', 'w': /* if sym.Type != Undefined && strings.Contains(sym.Name, "fprintf") { log.Printf("skip symbol type %c: %s\n", sym.Type, sym.Name) diff --git a/xtool/nm/query.go b/xtool/nm/nmindex/query.go similarity index 96% rename from xtool/nm/query.go rename to xtool/nm/nmindex/query.go index f39c19bc..d86c4d52 100644 --- a/xtool/nm/query.go +++ b/xtool/nm/nmindex/query.go @@ -14,19 +14,21 @@ * limitations under the License. */ -package nm +package nmindex import ( "bufio" "os" "strings" + + "github.com/goplus/llgo/xtool/nm" ) // MatchedItem represents a matched item type MatchedItem struct { ObjFile string Symbol string - Type SymbolType + Type nm.SymbolType } // MatchedFile represents a matched file @@ -88,7 +90,7 @@ func queryIndex(files []*MatchedFile, idxFile, query string) []*MatchedFile { items = append(items, &MatchedItem{ ObjFile: objFile, Symbol: sym, - Type: SymbolType(typ), + Type: nm.SymbolType(typ), }) } if len(items) > 0 {