diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index cd18a81a..89563e2a 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -11,19 +11,38 @@ on: jobs: - test-macos: - runs-on: macos-latest + test: strategy: matrix: + os: [macos-latest, macos-12, macos-13, ubuntu-latest] llvm: [17] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Update Homebrew - if: matrix.llvm == 17 # needed as long as LLVM 17 is still fresh + # needed as long as LLVM 17 is still fresh + if: matrix.llvm == 17 && startsWith(matrix.os, 'macos') run: brew update - - name: Install LLVM ${{ matrix.llvm }} - run: HOMEBREW_NO_AUTO_UPDATE=1 brew install llvm@${{ matrix.llvm }} + - name: Install LLVM ${{ matrix.llvm }} and bdw-gc + 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 + if: startsWith(matrix.os, 'ubuntu') + run: | + echo 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{ 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 --no-install-recommends clang-${{ matrix.llvm }} llvm-${{ matrix.llvm }}-dev libgc-dev + echo /usr/lib/llvm-${{ matrix.llvm }}/bin >> $GITHUB_PATH + + - name: Clang information + run: | + echo $PATH + which clang + clang --version - name: Set up Go uses: actions/setup-go@v5 @@ -34,36 +53,38 @@ jobs: run: go build -v ./... - name: Test + if: matrix.os != 'ubuntu-latest' run: go test -v ./... + - name: Test with coverage + if: matrix.os == 'ubuntu-latest' + run: go test -v -coverprofile="coverage.txt" -covermode=atomic ./... - test-linux: - runs-on: ubuntu-20.04 - strategy: - matrix: - llvm: [17] - steps: - - uses: actions/checkout@v4 + - name: Install + run: go install ./... + + - name: LLGO tests + 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 + + - name: Show test result + run: cat result.md - - name: Install LLVM ${{ matrix.llvm }} - run: | - echo 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{ 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 --no-install-recommends llvm-${{ matrix.llvm }}-dev + - name: PR comment with test result + uses: thollander/actions-comment-pull-request@v2 + if: false + with: + filePath: result.md + comment_tag: test-result-on-${{ matrix.os }}-with-llvm-${{ matrix.llvm }} - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.20' - - - name: Build - run: go build -v ./... - - - name: Test - run: go test -v -coverprofile="coverage.txt" -covermode=atomic ./... - - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - slug: goplus/llgo + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + slug: goplus/llgo diff --git a/.github/workflows/test_demo.sh b/.github/workflows/test_demo.sh new file mode 100644 index 00000000..6a4a5fb2 --- /dev/null +++ b/.github/workflows/test_demo.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# llgo run subdirectories under _demo and _pydemo +total=0 +failed=0 +failed_cases="" +for d in ./_demo/* ./_pydemo/*; do + total=$((total+1)) + if [ -d "$d" ]; then + echo "Testing $d" + if ! llgo run -v "$d"; then + echo "FAIL" + failed=$((failed+1)) + failed_cases="$failed_cases\n* :x: $d" + else + echo "PASS" + fi + fi +done +echo "=== Done" +echo "$((total-failed))/$total tests passed" + +if [ "$failed" -ne 0 ]; then + echo ":bangbang: Failed demo cases:" | tee -a result.md + echo -e "$failed_cases" | tee -a result.md + exit 1 +else + echo ":white_check_mark: All demo tests passed" | tee -a result.md +fi diff --git a/.github/workflows/test_llgo.sh b/.github/workflows/test_llgo.sh new file mode 100644 index 00000000..e1838fa5 --- /dev/null +++ b/.github/workflows/test_llgo.sh @@ -0,0 +1,38 @@ +#!/bin/bash +set -e + +export LLGOROOT=$PWD + +testcmd=/tmp/test +llgo build -o $testcmd ./_test +cases=$($testcmd) +total=$(echo "$cases" | wc -l | tr -d ' ') +failed=0 +failed_cases="" + +for idx in $(seq 1 $((total))); do + case=$(echo "$cases" | sed -n "${idx}p") + case_name=$(echo "$case" | cut -d',' -f2) + echo "=== Test case: $case_name" + set +e + out=$("$testcmd" "$((idx-1))" 2>&1) + exit_code=$? + set -e + if [ "${exit_code:-0}" -ne 0 ]; then + echo "failed: $out" + failed=$((failed+1)) + failed_cases="$failed_cases\n* :x: $case_name" + else + echo "passed" + fi +done +echo "=== Done" +echo "$((total-failed))/$total tests passed" + +if [ "$failed" -ne 0 ]; then + echo ":bangbang: Failed llgo cases:" | tee -a result.md + echo -e "$failed_cases" | tee -a result.md + exit 1 +else + echo ":white_check_mark: All llgo tests passed" | tee -a result.md +fi diff --git a/_test/bdwgc.go b/_test/bdwgc.go new file mode 100644 index 00000000..01bdcb2f --- /dev/null +++ b/_test/bdwgc.go @@ -0,0 +1,101 @@ +package main + +import ( + "unsafe" + + "github.com/goplus/llgo/_test/testing" + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/internal/runtime/bdwgc" +) + +// ------ Test malloc ------ + +func TestMalloc(t *testing.T) { + pn := (*int)(bdwgc.Malloc(unsafe.Sizeof(int(0)))) + *pn = 1 << 30 + c.Printf(c.Str("value: %d, %x, %p, %p\n"), *pn, *pn, pn, &pn) + + pl := (*int64)(bdwgc.Realloc(c.Pointer(pn), unsafe.Sizeof(int64(0)))) + *pl = 1 << 60 + c.Printf(c.Str("value: %lld, %llx, %p, %p\n"), *pl, *pl, pl, &pl) + + bdwgc.Free(c.Pointer(pl)) +} + +// ------ Test finalizer ------ + +const ( + RETURN_VALUE_FREED = 1 << 31 +) + +var called uint = 0 + +func setReturnValueFreed(pobj c.Pointer, clientData c.Pointer) { + called |= RETURN_VALUE_FREED + c.Printf(c.Str("called: %x\n"), called) +} + +func setLoopValueFreed(pobj c.Pointer, clientData c.Pointer) { + pmask := (*uint)(clientData) + called |= *pmask + c.Printf(c.Str("called: %x\n"), called) +} + +func isCalled(mask uint) bool { + return called&mask != 0 +} + +func returnValue() *int { + pn := bdwgc.Malloc(unsafe.Sizeof(int(0))) + bdwgc.RegisterFinalizer(pn, setReturnValueFreed, nil, nil, nil) + return (*int)(pn) +} + +func callFunc() { + pn := returnValue() + *pn = 1 << 30 + c.Printf(c.Str("value: %d, %x, %p, %p\n"), *pn, *pn, pn, &pn) + bdwgc.Gcollect() + check(!isCalled(RETURN_VALUE_FREED), c.Str("finalizer should not be called")) +} + +func loop() { + for i := 0; i < 5; i++ { + p := bdwgc.Malloc(unsafe.Sizeof(int(0))) + pn := (*int)(p) + *pn = i + c.Printf(c.Str("value: %d, %x, %p, %p\n"), *pn, *pn, pn, &pn) + pflag := (*uint)(c.Malloc(unsafe.Sizeof(uint(0)))) + *pflag = 1 << i + bdwgc.RegisterFinalizer(p, setLoopValueFreed, c.Pointer(pflag), nil, nil) + bdwgc.Gcollect() + check(!isCalled(1<= len(tests) { + c.Printf(c.Str("invalid test index %d"), idx) + panic("invalid test index") + } + tests[idx].F(nil) +} diff --git a/_test/testing/testing.go b/_test/testing/testing.go new file mode 100644 index 00000000..d4b6029b --- /dev/null +++ b/_test/testing/testing.go @@ -0,0 +1,4 @@ +package testing + +type T struct { +} diff --git a/c/c.go b/c/c.go index b3239ccd..55addd60 100644 --- a/c/c.go +++ b/c/c.go @@ -171,3 +171,6 @@ func GetoptLong(argc Int, argv **Char, optstring *Char, longopts *Option, longin func GetoptLongOnly(argc Int, argv **Char, optstring *Char, longopts *Option, longindex *Int) Int // ----------------------------------------------------------------------------- + +//go:linkname Atoi C.atoi +func Atoi(s *Char) Int diff --git a/internal/runtime/bdwgc/bdwgc.go b/internal/runtime/bdwgc/bdwgc.go new file mode 100644 index 00000000..f15c1bad --- /dev/null +++ b/internal/runtime/bdwgc/bdwgc.go @@ -0,0 +1,74 @@ +package bdwgc + +import "C" +import ( + _ "unsafe" + + "github.com/goplus/llgo/c" +) + +const ( + LLGoPackage = "link: $LLGO_LIB_BDWGC; $(pkg-config --libs bdw-gc); -lgc" +) + +// ----------------------------------------------------------------------------- + +//go:linkname Init C.GC_init +func Init() + +//go:linkname Malloc C.GC_malloc +func Malloc(size uintptr) c.Pointer + +//go:linkname Realloc C.GC_realloc +func Realloc(ptr c.Pointer, size uintptr) c.Pointer + +//go:linkname Free C.GC_free +func Free(ptr c.Pointer) + +// ----------------------------------------------------------------------------- + +//go:linkname RegisterFinalizer C.GC_register_finalizer +func RegisterFinalizer(obj c.Pointer, fn func(c.Pointer, c.Pointer), cd c.Pointer, old_fn *func(c.Pointer, c.Pointer), old_cd *c.Pointer) int + +//go:linkname RegisterFinalizerNoOrder C.GC_register_finalizer_no_order +func RegisterFinalizerNoOrder(obj c.Pointer, fn func(c.Pointer, c.Pointer), cd c.Pointer, old_fn *func(c.Pointer, c.Pointer), old_cd *c.Pointer) int + +//go:linkname RegisterFinalizerIgnoreSelf C.GC_register_finalizer_ignore_self +func RegisterFinalizerIgnoreSelf(obj c.Pointer, fn func(c.Pointer, c.Pointer), cd c.Pointer, old_fn *func(c.Pointer, c.Pointer), old_cd *c.Pointer) int + +//go:linkname RegisterFinalizerUnreachable C.GC_register_finalizer_unreachable +func RegisterFinalizerUnreachable(obj c.Pointer, fn func(c.Pointer, c.Pointer), cd c.Pointer, old_fn *func(c.Pointer, c.Pointer), old_cd *c.Pointer) int + +// ----------------------------------------------------------------------------- + +//go:linkname Enable C.GC_enable +func Enable() + +//go:linkname Disable C.GC_disable +func Disable() + +//go:linkname IsDisabled C.GC_is_disabled +func IsDisabled() int + +//go:linkname Gcollect C.GC_gcollect +func Gcollect() + +//go:linkname GetMemoryUse C.GC_get_memory_use +func GetMemoryUse() uintptr + +// ----------------------------------------------------------------------------- + +//go:linkname EnableIncremental C.GC_enable_incremental +func EnableIncremental() + +//go:linkname IsIncrementalMode C.GC_is_incremental_mode +func IsIncrementalMode() int + +//go:linkname IncrementalProtectionNeeds C.GC_incremental_protection_needs +func IncrementalProtectionNeeds() int + +//go:linkname StartIncrementalCollection C.GC_start_incremental_collection +func StartIncrementalCollection() + +//go:linkname CollectALittle C.GC_collect_a_little +func CollectALittle() diff --git a/internal/runtime/bdwgc/llgo_autogen.lla b/internal/runtime/bdwgc/llgo_autogen.lla new file mode 100644 index 00000000..5e1a1ad7 Binary files /dev/null and b/internal/runtime/bdwgc/llgo_autogen.lla differ diff --git a/internal/runtime/z_c.go b/internal/runtime/z_c.go index c2767b6c..29eb16ff 100644 --- a/internal/runtime/z_c.go +++ b/internal/runtime/z_c.go @@ -20,17 +20,18 @@ import ( "unsafe" "github.com/goplus/llgo/internal/abi" + "github.com/goplus/llgo/internal/runtime/bdwgc" "github.com/goplus/llgo/internal/runtime/c" ) // AllocU allocates uninitialized memory. func AllocU(size uintptr) unsafe.Pointer { - return c.Malloc(size) + return bdwgc.Malloc(size) } // AllocZ allocates zero-initialized memory. func AllocZ(size uintptr) unsafe.Pointer { - ret := c.Malloc(size) + ret := bdwgc.Malloc(size) return c.Memset(ret, 0, size) }