Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/llgo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,19 @@ jobs:
printf ' %s\n' "${selected[@]}"

# Run per-package and print elapsed time to keep logs readable.
std_pkgs=()
for pkg in "${selected[@]}"; do
echo "==> llgo test -timeout=20m -bench=^BenchmarkGo126 -benchtime=1x ${pkg}"
SECONDS=0
llgo test -timeout=20m -bench='^BenchmarkGo126' -benchtime=1x "${pkg}"
if [[ "${{ matrix.os }}" == ubuntu-latest && "${{ matrix.go }}" == 1.26.5 && "${pkg}" == */test/std/* ]]; then
std_pkgs+=("${pkg}")
fi
echo "==> done ${pkg} (${SECONDS}s)"
done
if [[ "${#std_pkgs[@]}" -ne 0 ]]; then
dev/test_std_buildmodes.sh "${std_pkgs[@]}"
fi

hello:
name: hello (${{ matrix.lane }}, ${{ matrix.os }}, LLVM ${{ matrix.llvm }}, Go ${{ matrix.go }})
Expand Down
6 changes: 6 additions & 0 deletions _demo/go/export/export.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"fmt"
"runtime"
"unsafe"

Expand Down Expand Up @@ -134,6 +135,11 @@ func RunGoroutine(value int) int {
return <-ch
}

//export FormatValue
func FormatValue(value string, number int) string {
return fmt.Sprintf("%s:%d", value, number)
}

// Functions with small struct parameters and return values

//export CreateSmallStruct
Expand Down
6 changes: 6 additions & 0 deletions _demo/go/export/libexport.h.want
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ mul(float a, float b);
void
github_com_goplus_llgo__demo_go_export_c_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/_demo/go/export/c.init")

intptr_t
AllThreadsSyscallStatus(void);

main_ComplexData
CreateComplexData(void);

Expand Down Expand Up @@ -163,6 +166,9 @@ CreateStringMap(void);
C_XType
CreateXType(int32_t id, GoString name, double value, _Bool flag);

GoString
FormatValue(GoString value, intptr_t number);

main_FuncInfoResult
GetFuncInfo(void);

Expand Down
28 changes: 28 additions & 0 deletions _demo/go/export/runtime_hooks_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//go:build linux

package main

import "syscall"

func gettimeofdayStatus() int {
var tv syscall.Timeval
if err := syscall.Gettimeofday(&tv); err != nil {
if errno, ok := err.(syscall.Errno); ok {
return int(errno)
}
return int(syscall.EINVAL)
}
if tv.Sec <= 0 {
return int(syscall.EINVAL)
}
return 0
}

//export AllThreadsSyscallStatus
func AllThreadsSyscallStatus() int {
if status := gettimeofdayStatus(); status != 0 {
return status
}
_, _, err := syscall.AllThreadsSyscall(syscall.SYS_GETPID, 0, 0, 0)
return int(err)
}
8 changes: 8 additions & 0 deletions _demo/go/export/runtime_hooks_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//go:build !linux

package main

//export AllThreadsSyscallStatus
func AllThreadsSyscallStatus() int {
return 0
}
3 changes: 2 additions & 1 deletion _demo/go/export/use/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ else
SHARED_EXT = so
PLATFORM_LIBS = $(shell pkg-config --libs libunwind 2>/dev/null || echo -lunwind)
endif
STATIC_STDLIB_LIBS = $(shell pkg-config --libs libffi 2>/dev/null || echo -lffi) -lresolv

# Library and flags based on link type
ifeq ($(LINK_TYPE),shared)
Expand All @@ -35,7 +36,7 @@ ifeq ($(LINK_TYPE),shared)
else
BUILDMODE = c-archive
LIBRARY = ../libexport.a
LDFLAGS = $(LIBRARY) $(RUNTIME_LIBS) $(PLATFORM_LIBS)
LDFLAGS = $(LIBRARY) $(RUNTIME_LIBS) $(PLATFORM_LIBS) $(STATIC_STDLIB_LIBS)
BUILD_MSG = "Building Go static library..."
LINK_MSG = "Linking with static library..."
endif
Expand Down
11 changes: 11 additions & 0 deletions _demo/go/export/use/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <stdlib.h>
#include <inttypes.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include "../libexport.h"

Expand Down Expand Up @@ -59,6 +60,16 @@ int main() {
HelloWorld();
printf("\n");

// Verify that a C library can initialize and call standard-library paths
// that depend on the runtime hooks supplied by LLGo.
GoString formatted = FormatValue((GoString){"answer", 6}, 42);
assert(go_string_equals(formatted, "answer:42"));
#ifdef __linux__
assert(AllThreadsSyscallStatus() == ENOTSUP);
#else
assert(AllThreadsSyscallStatus() == 0);
#endif

// Test small struct
main_SmallStruct small = CreateSmallStruct(5, 1); // 1 for true
assert(small.ID == 5);
Expand Down
3 changes: 2 additions & 1 deletion cmd/internal/test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func init() {
flags.AddCommonFlags(&Cmd.Flag)
flags.AddCompilerVerboseFlag(&Cmd.Flag)
flags.AddBuildFlags(&Cmd.Flag)
flags.AddBuildModeFlags(&Cmd.Flag)
flags.AddTestFlags(&Cmd.Flag)
flags.AddTestBinaryFlags(&Cmd.Flag)
flags.AddEmulatorFlags(&Cmd.Flag)
Expand All @@ -43,7 +44,7 @@ func runCmd(cmd *base.Command, args []string) {
}

conf := build.NewDefaultConf(build.ModeTest)
if err := flags.UpdateConfig(conf); err != nil {
if err := flags.UpdateBuildConfig(conf); err != nil {
fmt.Fprintln(os.Stderr, err)
mockable.Exit(1)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/internal/test/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

func TestBuildFlagsWiring(t *testing.T) {
if goBuildFlags.Flag != &Cmd.Flag || Cmd.Flag.Lookup("ldflags") == nil {
if goBuildFlags.Flag != &Cmd.Flag || Cmd.Flag.Lookup("ldflags") == nil || Cmd.Flag.Lookup("buildmode") == nil {
t.Fatal("test build flags are not bound to the test command")
}
}
Expand Down
135 changes: 135 additions & 0 deletions dev/test_std_buildmodes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/bin/bash

set -euo pipefail

if [[ $# -eq 0 ]]; then
echo "usage: $0 ./test/std/package [...]" >&2
exit 2
fi

root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
test_pkgs=("$@")
import_paths=()
stems=()
groups=()
max_group=0
for test_pkg in "${test_pkgs[@]}"; do
read -r import_path package_dir < <(go list -tags=llgo -f '{{.ImportPath}} {{.Dir}}' "${test_pkg}")
case "${import_path}" in
github.com/goplus/llgo/test/std/*) ;;
*)
echo "not a test/std package: ${test_pkg}" >&2
exit 2
;;
esac
stem="$(basename "${package_dir}").test"
group=0
for i in "${!stems[@]}"; do
if [[ "${stems[$i]}" == "${stem}" ]]; then
group=$((group + 1))
fi
done
import_paths+=("${import_path}")
stems+=("${stem}")
groups+=("${group}")
if (( group > max_group )); then
max_group="${group}"
fi
done

# Keep the unique package in its own compiler invocation. Its generic use of
# weak.Pointer can otherwise assign shared instantiations to libunique.test and
# leave another output with unresolved weak.Pointer methods.
for i in "${!import_paths[@]}"; do
if [[ "${import_paths[$i]}" == "github.com/goplus/llgo/test/std/unique" ]]; then
max_group=$((max_group + 1))
groups[$i]="${max_group}"
fi
done

llgo_cmd="${LLGO:-llgo}"
if [[ "${llgo_cmd}" == */* && "${llgo_cmd}" != /* ]]; then
llgo_cmd="$(cd "$(dirname "${llgo_cmd}")" && pwd)/$(basename "${llgo_cmd}")"
fi
runner_source="${root_dir}/dev/test_std_buildmodes/runner.c"

runtime_libs=(-lpthread -lm -lresolv)
if [[ "$(uname -s)" == Darwin ]]; then
runtime_libs+=(-framework CoreFoundation -framework Security)
fi
for dependency in bdw-gc libuv libffi; do
if pkg-config --exists "${dependency}"; then
while IFS= read -r flag; do
[[ -n "${flag}" ]] && runtime_libs+=("${flag}")
done < <(pkg-config --libs "${dependency}" | xargs -n1)
fi
done
if [[ "$(uname -s)" != Darwin ]] && pkg-config --exists libunwind; then
while IFS= read -r flag; do
[[ -n "${flag}" ]] && runtime_libs+=("${flag}")
done < <(pkg-config --libs libunwind | xargs -n1)
fi

work_dir="$(mktemp -d "${root_dir}/.std-buildmodes.XXXXXX")"
trap 'rm -rf "${work_dir}"' EXIT

for mode in c-shared c-archive; do
for ((group = 0; group <= max_group; group++)); do
group_imports=()
for i in "${!import_paths[@]}"; do
if [[ "${groups[$i]}" -eq "${group}" ]]; then
group_imports+=("${import_paths[$i]}")
fi
done
if [[ "${#group_imports[@]}" -eq 0 ]]; then
continue
fi
echo "==> ${mode}: compile ${#group_imports[@]} test package(s)"
(
cd "${work_dir}"
"${llgo_cmd}" test -c -buildmode="${mode}" "${group_imports[@]}"
)

for i in "${!import_paths[@]}"; do
if [[ "${groups[$i]}" -ne "${group}" ]]; then
continue
fi
import_path="${import_paths[$i]}"
stem="${stems[$i]}"
test_main_pkg="${import_path}.test"
runner_base="${work_dir}/runner-${i}"
echo "==> ${test_pkgs[$i]}: run ${mode}"
clang -x c -c "${runner_source}" \
"-DGO_TEST_PACKAGE=\"${test_main_pkg}\"" \
-o "${runner_base}.o"

if [[ "${mode}" == c-shared ]]; then
if [[ "$(uname -s)" == Darwin ]]; then
library="${work_dir}/lib${stem}.dylib"
else
library="${work_dir}/lib${stem}.so"
fi
clang++ "${runner_base}.o" -o "${runner_base}" \
-L"${work_dir}" "-l${stem}" "${runtime_libs[@]}"
LD_LIBRARY_PATH="${work_dir}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" \
DYLD_LIBRARY_PATH="${work_dir}${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}" \
"${runner_base}"
else
library="${work_dir}/lib${stem}.a"
clang++ "${runner_base}.o" -o "${runner_base}" \
"${library}" "${runtime_libs[@]}"
"${runner_base}"
fi

if [[ ! -s "${library}" ]]; then
echo "missing ${mode} library: ${library}" >&2
exit 1
fi
for artifact in "${runner_base}" "${runner_base}.o" "${library}" "${work_dir}/lib${stem}.h"; do
if [[ -e "${artifact}" ]]; then
unlink "${artifact}"
fi
done
done
done
done
22 changes: 22 additions & 0 deletions dev/test_std_buildmodes/runner.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#ifndef GO_TEST_PACKAGE
#error GO_TEST_PACKAGE must name the generated Go test main package
#endif

#ifdef __APPLE__
#define GO_SYMBOL(name) __asm__("_" name)
#else
#define GO_SYMBOL(name) __asm__(name)
#endif

extern void llgo_test_init(void) GO_SYMBOL(GO_TEST_PACKAGE ".init");
extern void llgo_test_run(void) GO_SYMBOL(GO_TEST_PACKAGE ".main");
extern int __llgo_argc;
extern char **__llgo_argv;

int main(int argc, char **argv) {
__llgo_argc = argc;
__llgo_argv = argv;
llgo_test_init();
llgo_test_run();
return 0;
}
27 changes: 27 additions & 0 deletions doc/_readme/scripts/check_std_cover.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,42 @@ if [ "${#packages[@]}" -eq 0 ]; then
fi

args=()
covered_packages=()
for pkg in "${packages[@]}"; do
rel_path="${pkg#${module_path}/}"
if [[ "${rel_path}" != test/std/* ]]; then
continue
fi
stdlib_pkg="${rel_path#test/std/}"
covered_packages+=("${stdlib_pkg}")
if [[ "${stdlib_pkg}" == "runtime" ]]; then
continue
fi
args+=("-pkg" "${stdlib_pkg}")
done

expected_file="$(mktemp)"
covered_file="$(mktemp)"
trap 'rm -f "${expected_file}" "${covered_file}"' EXIT

go list std \
| awk '!/(^|\/)internal(\/|$)/ && !/(^|\/)vendor(\/|$)/' \
| sort -u > "${expected_file}"
printf '%s\n' "${covered_packages[@]}" | sort -u > "${covered_file}"

missing_packages="$(comm -23 "${expected_file}" "${covered_file}")"
if [[ -n "${missing_packages}" ]]; then
echo "Public standard-library packages missing test/std coverage:" >&2
while IFS= read -r pkg; do
echo " - ${pkg}" >&2
done <<< "${missing_packages}"
exit 1
fi

expected_count="$(wc -l < "${expected_file}" | tr -d ' ')"
covered_count="$(wc -l < "${covered_file}" | tr -d ' ')"
echo "Public standard-library package coverage: ${covered_count}/${expected_count}"

printf '+ go run ./chore/check_std_symbols'
for arg in "${args[@]}"; do
printf ' %q' "${arg}"
Expand Down
10 changes: 7 additions & 3 deletions internal/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -1349,9 +1349,9 @@ func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose
return cmd.Link(buildArgs...)
}

// cSharedExportArgs keeps //export functions as shared-library link roots. The
// functions live in package archives and otherwise remain unreferenced, so the
// linker can omit both their object files and dynamic symbols.
// cSharedExportArgs keeps //export functions and synthetic test entry points as
// shared-library link roots. They live in package archives and otherwise remain
// unreferenced, so the linker can omit both their object files and symbols.
func cSharedExportArgs(ctx *context, pkgs []*aPackage) []string {
if ctx == nil || ctx.buildConf == nil || ctx.buildConf.BuildMode != BuildModeCShared {
return nil
Expand All @@ -1366,6 +1366,10 @@ func cSharedExportArgs(ctx *context, pkgs []*aPackage) []string {
exports[name] = none{}
}
}
if ctx.mode == ModeTest && pkg.Package != nil && pkg.Name == "main" && strings.HasSuffix(pkg.PkgPath, ".test") {
exports[pkg.PkgPath+".init"] = none{}
exports[pkg.PkgPath+".main"] = none{}
}
}
names := make([]string, 0, len(exports))
for name := range exports {
Expand Down
Loading
Loading