Compare commits

...

2 Commits

Author SHA1 Message Date
6b9e2a0e20 op 1 2025-04-04 21:32:17 -05:00
e58f9a6028 add bench profiling 2025-04-04 21:32:17 -05:00
7 changed files with 544 additions and 178 deletions

2
.gitignore vendored
View File

@ -21,3 +21,5 @@
go.work go.work
.idea .idea
bench/profile_results

171
bench/bench_profile.go Normal file
View File

@ -0,0 +1,171 @@
package luajit_bench
import (
"flag"
"fmt"
"os"
"runtime"
"runtime/pprof"
"testing"
)
// Profiling flags
var (
cpuProfile = flag.String("cpuprofile", "", "write cpu profile to `file`")
memProfile = flag.String("memprofile", "", "write memory profile to `file`")
memProfileGC = flag.Bool("memprofilegc", false, "force GC before writing memory profile")
blockProfile = flag.String("blockprofile", "", "write block profile to `file`")
mutexProfile = flag.String("mutexprofile", "", "write mutex profile to `file`")
)
// setupTestMain configures profiling for benchmarks
func setupTestMain() {
// Make sure the flags are parsed
if !flag.Parsed() {
flag.Parse()
}
// CPU profiling
if *cpuProfile != "" {
f, err := os.Create(*cpuProfile)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create CPU profile: %v\n", err)
os.Exit(1)
}
if err := pprof.StartCPUProfile(f); err != nil {
fmt.Fprintf(os.Stderr, "Failed to start CPU profile: %v\n", err)
os.Exit(1)
}
fmt.Println("CPU profiling enabled")
}
// Block profiling (goroutine blocking)
if *blockProfile != "" {
runtime.SetBlockProfileRate(1)
fmt.Println("Block profiling enabled")
}
// Mutex profiling (lock contention)
if *mutexProfile != "" {
runtime.SetMutexProfileFraction(1)
fmt.Println("Mutex profiling enabled")
}
}
// teardownTestMain completes profiling and writes output files
func teardownTestMain() {
// Stop CPU profile
if *cpuProfile != "" {
pprof.StopCPUProfile()
fmt.Println("CPU profile written to", *cpuProfile)
}
// Write memory profile
if *memProfile != "" {
f, err := os.Create(*memProfile)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create memory profile: %v\n", err)
os.Exit(1)
}
defer f.Close()
// Force garbage collection before writing memory profile if requested
if *memProfileGC {
runtime.GC()
}
if err := pprof.WriteHeapProfile(f); err != nil {
fmt.Fprintf(os.Stderr, "Failed to write memory profile: %v\n", err)
os.Exit(1)
}
fmt.Println("Memory profile written to", *memProfile)
}
// Write block profile
if *blockProfile != "" {
f, err := os.Create(*blockProfile)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create block profile: %v\n", err)
os.Exit(1)
}
defer f.Close()
if err := pprof.Lookup("block").WriteTo(f, 0); err != nil {
fmt.Fprintf(os.Stderr, "Failed to write block profile: %v\n", err)
os.Exit(1)
}
fmt.Println("Block profile written to", *blockProfile)
}
// Write mutex profile
if *mutexProfile != "" {
f, err := os.Create(*mutexProfile)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create mutex profile: %v\n", err)
os.Exit(1)
}
defer f.Close()
if err := pprof.Lookup("mutex").WriteTo(f, 0); err != nil {
fmt.Fprintf(os.Stderr, "Failed to write mutex profile: %v\n", err)
os.Exit(1)
}
fmt.Println("Mutex profile written to", *mutexProfile)
}
}
// TestMain is the entry point for all tests in this package
func TestMain(m *testing.M) {
setupTestMain()
code := m.Run()
teardownTestMain()
os.Exit(code)
}
// MemStats captures a snapshot of memory statistics
type MemStats struct {
Alloc uint64
TotalAlloc uint64
Sys uint64
Mallocs uint64
Frees uint64
HeapAlloc uint64
}
// CaptureMemStats returns current memory statistics
func CaptureMemStats() MemStats {
var m runtime.MemStats
runtime.ReadMemStats(&m)
return MemStats{
Alloc: m.Alloc,
TotalAlloc: m.TotalAlloc,
Sys: m.Sys,
Mallocs: m.Mallocs,
Frees: m.Frees,
HeapAlloc: m.HeapAlloc,
}
}
// TrackMemoryUsage runs fn and reports memory usage before and after
func TrackMemoryUsage(b *testing.B, name string, fn func()) {
b.Helper()
// Force GC before measurement
runtime.GC()
// Capture memory stats before
before := CaptureMemStats()
// Run the function
fn()
// Force GC after measurement to get accurate stats
runtime.GC()
// Capture memory stats after
after := CaptureMemStats()
// Report stats
b.ReportMetric(float64(after.Mallocs-before.Mallocs), name+"-mallocs")
b.ReportMetric(float64(after.TotalAlloc-before.TotalAlloc)/float64(b.N), name+"-bytes/op")
}

View File

@ -16,11 +16,14 @@ func BenchmarkSimpleDoString(b *testing.B) {
code := "local x = 1 + 1" code := "local x = 1 + 1"
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := state.DoString(code); err != nil { TrackMemoryUsage(b, "dostring", func() {
b.Fatalf("DoString failed: %v", err) for i := 0; i < b.N; i++ {
if err := state.DoString(code); err != nil {
b.Fatalf("DoString failed: %v", err)
}
} }
} })
} }
// BenchmarkSimpleCompileAndRun benchmarks compile and run of a simple expression // BenchmarkSimpleCompileAndRun benchmarks compile and run of a simple expression
@ -33,11 +36,14 @@ func BenchmarkSimpleCompileAndRun(b *testing.B) {
code := "local x = 1 + 1" code := "local x = 1 + 1"
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := state.CompileAndRun(code, "simple"); err != nil { TrackMemoryUsage(b, "compile-run", func() {
b.Fatalf("CompileAndRun failed: %v", err) for i := 0; i < b.N; i++ {
if err := state.CompileAndRun(code, "simple"); err != nil {
b.Fatalf("CompileAndRun failed: %v", err)
}
} }
} })
} }
// BenchmarkSimpleCompileLoadRun benchmarks compile, load, and run of a simple expression // BenchmarkSimpleCompileLoadRun benchmarks compile, load, and run of a simple expression
@ -50,15 +56,18 @@ func BenchmarkSimpleCompileLoadRun(b *testing.B) {
code := "local x = 1 + 1" code := "local x = 1 + 1"
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ {
bytecode, err := state.CompileBytecode(code, "simple") TrackMemoryUsage(b, "compile-load-run", func() {
if err != nil { for i := 0; i < b.N; i++ {
b.Fatalf("CompileBytecode failed: %v", err) bytecode, err := state.CompileBytecode(code, "simple")
if err != nil {
b.Fatalf("CompileBytecode failed: %v", err)
}
if err := state.LoadAndRunBytecode(bytecode, "simple"); err != nil {
b.Fatalf("LoadAndRunBytecode failed: %v", err)
}
} }
if err := state.LoadAndRunBytecode(bytecode, "simple"); err != nil { })
b.Fatalf("LoadAndRunBytecode failed: %v", err)
}
}
} }
// BenchmarkSimplePrecompiledBytecode benchmarks running precompiled bytecode // BenchmarkSimplePrecompiledBytecode benchmarks running precompiled bytecode
@ -75,11 +84,14 @@ func BenchmarkSimplePrecompiledBytecode(b *testing.B) {
b.Fatalf("CompileBytecode failed: %v", err) b.Fatalf("CompileBytecode failed: %v", err)
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := state.LoadAndRunBytecode(bytecode, "simple"); err != nil { TrackMemoryUsage(b, "precompiled", func() {
b.Fatalf("LoadAndRunBytecode failed: %v", err) for i := 0; i < b.N; i++ {
if err := state.LoadAndRunBytecode(bytecode, "simple"); err != nil {
b.Fatalf("LoadAndRunBytecode failed: %v", err)
}
} }
} })
} }
// BenchmarkFunctionCallDoString benchmarks direct execution of a function call // BenchmarkFunctionCallDoString benchmarks direct execution of a function call
@ -102,11 +114,14 @@ func BenchmarkFunctionCallDoString(b *testing.B) {
code := "local result = add(10, 20)" code := "local result = add(10, 20)"
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := state.DoString(code); err != nil { TrackMemoryUsage(b, "func-dostring", func() {
b.Fatalf("DoString failed: %v", err) for i := 0; i < b.N; i++ {
if err := state.DoString(code); err != nil {
b.Fatalf("DoString failed: %v", err)
}
} }
} })
} }
// BenchmarkFunctionCallPrecompiled benchmarks precompiled function call // BenchmarkFunctionCallPrecompiled benchmarks precompiled function call
@ -133,11 +148,14 @@ func BenchmarkFunctionCallPrecompiled(b *testing.B) {
b.Fatalf("CompileBytecode failed: %v", err) b.Fatalf("CompileBytecode failed: %v", err)
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := state.LoadAndRunBytecode(bytecode, "call"); err != nil { TrackMemoryUsage(b, "func-precompiled", func() {
b.Fatalf("LoadAndRunBytecode failed: %v", err) for i := 0; i < b.N; i++ {
if err := state.LoadAndRunBytecode(bytecode, "call"); err != nil {
b.Fatalf("LoadAndRunBytecode failed: %v", err)
}
} }
} })
} }
// BenchmarkLoopDoString benchmarks direct execution of a loop // BenchmarkLoopDoString benchmarks direct execution of a loop
@ -155,11 +173,14 @@ func BenchmarkLoopDoString(b *testing.B) {
end end
` `
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := state.DoString(code); err != nil { TrackMemoryUsage(b, "loop-dostring", func() {
b.Fatalf("DoString failed: %v", err) for i := 0; i < b.N; i++ {
if err := state.DoString(code); err != nil {
b.Fatalf("DoString failed: %v", err)
}
} }
} })
} }
// BenchmarkLoopPrecompiled benchmarks precompiled loop execution // BenchmarkLoopPrecompiled benchmarks precompiled loop execution
@ -181,11 +202,14 @@ func BenchmarkLoopPrecompiled(b *testing.B) {
b.Fatalf("CompileBytecode failed: %v", err) b.Fatalf("CompileBytecode failed: %v", err)
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := state.LoadAndRunBytecode(bytecode, "loop"); err != nil { TrackMemoryUsage(b, "loop-precompiled", func() {
b.Fatalf("LoadAndRunBytecode failed: %v", err) for i := 0; i < b.N; i++ {
if err := state.LoadAndRunBytecode(bytecode, "loop"); err != nil {
b.Fatalf("LoadAndRunBytecode failed: %v", err)
}
} }
} })
} }
// BenchmarkTableOperationsDoString benchmarks direct execution of table operations // BenchmarkTableOperationsDoString benchmarks direct execution of table operations
@ -207,11 +231,14 @@ func BenchmarkTableOperationsDoString(b *testing.B) {
end end
` `
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := state.DoString(code); err != nil { TrackMemoryUsage(b, "table-dostring", func() {
b.Fatalf("DoString failed: %v", err) for i := 0; i < b.N; i++ {
if err := state.DoString(code); err != nil {
b.Fatalf("DoString failed: %v", err)
}
} }
} })
} }
// BenchmarkTableOperationsPrecompiled benchmarks precompiled table operations // BenchmarkTableOperationsPrecompiled benchmarks precompiled table operations
@ -237,11 +264,14 @@ func BenchmarkTableOperationsPrecompiled(b *testing.B) {
b.Fatalf("CompileBytecode failed: %v", err) b.Fatalf("CompileBytecode failed: %v", err)
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := state.LoadAndRunBytecode(bytecode, "table"); err != nil { TrackMemoryUsage(b, "table-precompiled", func() {
b.Fatalf("LoadAndRunBytecode failed: %v", err) for i := 0; i < b.N; i++ {
if err := state.LoadAndRunBytecode(bytecode, "table"); err != nil {
b.Fatalf("LoadAndRunBytecode failed: %v", err)
}
} }
} })
} }
// BenchmarkGoFunctionCall benchmarks calling a Go function from Lua // BenchmarkGoFunctionCall benchmarks calling a Go function from Lua
@ -269,11 +299,14 @@ func BenchmarkGoFunctionCall(b *testing.B) {
b.Fatalf("CompileBytecode failed: %v", err) b.Fatalf("CompileBytecode failed: %v", err)
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := state.LoadAndRunBytecode(bytecode, "gofunc"); err != nil { TrackMemoryUsage(b, "go-func-call", func() {
b.Fatalf("LoadAndRunBytecode failed: %v", err) for i := 0; i < b.N; i++ {
if err := state.LoadAndRunBytecode(bytecode, "gofunc"); err != nil {
b.Fatalf("LoadAndRunBytecode failed: %v", err)
}
} }
} })
} }
// BenchmarkComplexScript benchmarks a more complex script // BenchmarkComplexScript benchmarks a more complex script
@ -322,11 +355,14 @@ func BenchmarkComplexScript(b *testing.B) {
return result return result
` `
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err := state.ExecuteWithResult(code); err != nil { TrackMemoryUsage(b, "complex-script", func() {
b.Fatalf("ExecuteWithResult failed: %v", err) for i := 0; i < b.N; i++ {
if _, err := state.ExecuteWithResult(code); err != nil {
b.Fatalf("ExecuteWithResult failed: %v", err)
}
} }
} })
} }
// BenchmarkComplexScriptPrecompiled benchmarks a precompiled complex script // BenchmarkComplexScriptPrecompiled benchmarks a precompiled complex script
@ -379,15 +415,18 @@ func BenchmarkComplexScriptPrecompiled(b *testing.B) {
b.Fatalf("CompileBytecode failed: %v", err) b.Fatalf("CompileBytecode failed: %v", err)
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := state.LoadBytecode(bytecode, "complex"); err != nil { TrackMemoryUsage(b, "complex-precompiled", func() {
b.Fatalf("LoadBytecode failed: %v", err) for i := 0; i < b.N; i++ {
if err := state.LoadBytecode(bytecode, "complex"); err != nil {
b.Fatalf("LoadBytecode failed: %v", err)
}
if err := state.RunBytecodeWithResults(1); err != nil {
b.Fatalf("RunBytecodeWithResults failed: %v", err)
}
state.Pop(1) // Pop the result
} }
if err := state.RunBytecodeWithResults(1); err != nil { // Assuming this method exists to get the return value })
b.Fatalf("RunBytecodeWithResults failed: %v", err)
}
state.Pop(1) // Pop the result
}
} }
// BenchmarkMultipleExecutions benchmarks executing the same bytecode multiple times // BenchmarkMultipleExecutions benchmarks executing the same bytecode multiple times
@ -418,13 +457,16 @@ func BenchmarkMultipleExecutions(b *testing.B) {
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := state.LoadBytecode(bytecode, "increment"); err != nil { TrackMemoryUsage(b, "multiple-executions", func() {
b.Fatalf("LoadBytecode failed: %v", err) for i := 0; i < b.N; i++ {
if err := state.LoadBytecode(bytecode, "increment"); err != nil {
b.Fatalf("LoadBytecode failed: %v", err)
}
if err := state.RunBytecodeWithResults(1); err != nil {
b.Fatalf("RunBytecodeWithResults failed: %v", err)
}
state.Pop(1) // Pop the result
} }
if err := state.RunBytecodeWithResults(1); err != nil { // Assuming this method exists })
b.Fatalf("RunBytecodeWithResults failed: %v", err)
}
state.Pop(1) // Pop the result
}
} }

View File

@ -72,14 +72,17 @@ func BenchmarkLuaDirectExecution(b *testing.B) {
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ {
// Execute string and get results TrackMemoryUsage(b, "direct-"+bc.name, func() {
nresults, err := L.Execute(bc.code) for i := 0; i < b.N; i++ {
if err != nil { // Execute string and get results
b.Fatalf("Failed to execute code: %v", err) nresults, err := L.Execute(bc.code)
if err != nil {
b.Fatalf("Failed to execute code: %v", err)
}
L.Pop(nresults) // Clean up any results
} }
L.Pop(nresults) // Clean up any results })
}
}) })
} }
} }
@ -123,11 +126,13 @@ func BenchmarkLuaBytecodeExecution(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.SetBytes(int64(len(bytecode))) // Track bytecode size in benchmarks b.SetBytes(int64(len(bytecode))) // Track bytecode size in benchmarks
for i := 0; i < b.N; i++ { TrackMemoryUsage(b, "bytecode-"+bc.name, func() {
if err := L.LoadAndRunBytecode(bytecode, bc.name); err != nil { for i := 0; i < b.N; i++ {
b.Fatalf("Error executing bytecode: %v", err) if err := L.LoadAndRunBytecode(bytecode, bc.name); err != nil {
b.Fatalf("Error executing bytecode: %v", err)
}
} }
} })
}) })
} }
} }

78
bench/profile.sh Executable file
View File

@ -0,0 +1,78 @@
#!/bin/bash
# Easy script to run benchmarks with profiling enabled
# Usage: ./profile_benchmarks.sh [benchmark_pattern]
set -e
# Default values
BENCHMARK=${1:-"."}
OUTPUT_DIR="./profile_results"
CPU_PROFILE="$OUTPUT_DIR/cpu.prof"
MEM_PROFILE="$OUTPUT_DIR/mem.prof"
BLOCK_PROFILE="$OUTPUT_DIR/block.prof"
MUTEX_PROFILE="$OUTPUT_DIR/mutex.prof"
TRACE_FILE="$OUTPUT_DIR/trace.out"
HTML_OUTPUT="$OUTPUT_DIR/profile_report.html"
# Create output directory
mkdir -p "$OUTPUT_DIR"
echo "Running benchmarks with profiling enabled..."
# Run benchmarks with profiling flags
go test -bench="$BENCHMARK" -benchmem -cpuprofile="$CPU_PROFILE" -memprofile="$MEM_PROFILE" -blockprofile="$BLOCK_PROFILE" -mutexprofile="$MUTEX_PROFILE" -count=5 -timeout=30m
echo "Generating CPU profile analysis..."
go tool pprof -http=":1880" -output="$OUTPUT_DIR/cpu_graph.svg" "$CPU_PROFILE"
echo "Generating memory profile analysis..."
go tool pprof -http=":1880" -output="$OUTPUT_DIR/mem_graph.svg" "$MEM_PROFILE"
# Generate a simple HTML report
cat > "$HTML_OUTPUT" << EOF
<!DOCTYPE html>
<html>
<head>
<title>LuaJIT Benchmark Profiling Results</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1, h2 { color: #333; }
.profile { margin-bottom: 30px; }
img { max-width: 100%; border: 1px solid #ddd; }
</style>
</head>
<body>
<h1>LuaJIT Benchmark Profiling Results</h1>
<p>Generated on: $(date)</p>
<div class="profile">
<h2>CPU Profile</h2>
<img src="cpu_graph.svg" alt="CPU Profile Graph">
<p>Command to explore: <code>go tool pprof $CPU_PROFILE</code></p>
</div>
<div class="profile">
<h2>Memory Profile</h2>
<img src="mem_graph.svg" alt="Memory Profile Graph">
<p>Command to explore: <code>go tool pprof $MEM_PROFILE</code></p>
</div>
<div class="profile">
<h2>Tips for Profile Analysis</h2>
<ul>
<li>Use <code>go tool pprof -http=:8080 $CPU_PROFILE</code> for interactive web UI</li>
<li>Use <code>top10</code> in pprof to see the top 10 functions by CPU/memory usage</li>
<li>Use <code>list FunctionName</code> to see line-by-line stats for a specific function</li>
</ul>
</div>
</body>
</html>
EOF
echo "Profiling complete! Results available in $OUTPUT_DIR"
echo "View the HTML report at $HTML_OUTPUT"
echo ""
echo "For detailed interactive analysis, run:"
echo " go tool pprof -http=:1880 $CPU_PROFILE # For CPU profile"
echo " go tool pprof -http=:1880 $MEM_PROFILE # For memory profile"

View File

@ -38,6 +38,16 @@ int direct_bytecode_writer(lua_State *L, const void *p, size_t sz, void *ud) {
data[1] = (void*)(current_size + sz); data[1] = (void*)(current_size + sz);
return 0; return 0;
} }
// Combined load and run bytecode in a single call
int load_and_run_bytecode(lua_State *L, const unsigned char *buf, size_t len,
const char *name, int nresults) {
BytecodeReader reader = {buf, len, name};
int status = lua_load(L, bytecode_reader, &reader, name);
if (status != 0) return status;
return lua_pcall(L, 0, nresults, 0);
}
*/ */
import "C" import "C"
import ( import (
@ -55,9 +65,10 @@ func (s *State) CompileBytecode(code string, name string) ([]byte, error) {
data := [2]unsafe.Pointer{nil, nil} data := [2]unsafe.Pointer{nil, nil}
// Dump the function to bytecode // Dump the function to bytecode
err := s.safeCall(func() C.int { status := C.lua_dump(s.L, (*[0]byte)(unsafe.Pointer(C.direct_bytecode_writer)), unsafe.Pointer(&data))
return C.lua_dump(s.L, (*[0]byte)(unsafe.Pointer(C.direct_bytecode_writer)), unsafe.Pointer(&data)) if status != 0 {
}) return nil, fmt.Errorf("failed to dump bytecode: status %d", status)
}
// Get result // Get result
var bytecode []byte var bytecode []byte
@ -70,10 +81,6 @@ func (s *State) CompileBytecode(code string, name string) ([]byte, error) {
s.Pop(1) // Remove the function from stack s.Pop(1) // Remove the function from stack
if err != nil {
return nil, fmt.Errorf("failed to dump bytecode: %w", err)
}
return bytecode, nil return bytecode, nil
} }
@ -87,17 +94,20 @@ func (s *State) LoadBytecode(bytecode []byte, name string) error {
defer C.free(unsafe.Pointer(cname)) defer C.free(unsafe.Pointer(cname))
// Load the bytecode // Load the bytecode
err := s.safeCall(func() C.int { status := C.load_bytecode(
return C.load_bytecode( s.L,
s.L, (*C.uchar)(unsafe.Pointer(&bytecode[0])),
(*C.uchar)(unsafe.Pointer(&bytecode[0])), C.size_t(len(bytecode)),
C.size_t(len(bytecode)), cname,
cname, )
)
})
if err != nil { if status != 0 {
return fmt.Errorf("failed to load bytecode: %w", err) err := &LuaError{
Code: int(status),
Message: s.ToString(-1),
}
s.Pop(1) // Remove error message
return err
} }
return nil return nil
@ -111,25 +121,76 @@ func (s *State) RunBytecode() error {
// RunBytecodeWithResults executes bytecode and keeps nresults on the stack // RunBytecodeWithResults executes bytecode and keeps nresults on the stack
// Use LUA_MULTRET (-1) to keep all results // Use LUA_MULTRET (-1) to keep all results
func (s *State) RunBytecodeWithResults(nresults int) error { func (s *State) RunBytecodeWithResults(nresults int) error {
return s.safeCall(func() C.int { status := C.lua_pcall(s.L, 0, C.int(nresults), 0)
return C.lua_pcall(s.L, 0, C.int(nresults), 0) if status != 0 {
}) err := &LuaError{
} Code: int(status),
Message: s.ToString(-1),
// LoadAndRunBytecode loads and executes bytecode }
func (s *State) LoadAndRunBytecode(bytecode []byte, name string) error { s.Pop(1) // Remove error message
if err := s.LoadBytecode(bytecode, name); err != nil {
return err return err
} }
return s.RunBytecode() return nil
}
// LoadAndRunBytecode loads and executes bytecode in a single CGO transition
func (s *State) LoadAndRunBytecode(bytecode []byte, name string) error {
if len(bytecode) == 0 {
return fmt.Errorf("empty bytecode")
}
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
// Use combined load and run function
status := C.load_and_run_bytecode(
s.L,
(*C.uchar)(unsafe.Pointer(&bytecode[0])),
C.size_t(len(bytecode)),
cname,
0, // No results
)
if status != 0 {
err := &LuaError{
Code: int(status),
Message: s.ToString(-1),
}
s.Pop(1) // Remove error message
return err
}
return nil
} }
// LoadAndRunBytecodeWithResults loads and executes bytecode, preserving results // LoadAndRunBytecodeWithResults loads and executes bytecode, preserving results
func (s *State) LoadAndRunBytecodeWithResults(bytecode []byte, name string, nresults int) error { func (s *State) LoadAndRunBytecodeWithResults(bytecode []byte, name string, nresults int) error {
if err := s.LoadBytecode(bytecode, name); err != nil { if len(bytecode) == 0 {
return fmt.Errorf("empty bytecode")
}
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
// Use combined load and run function
status := C.load_and_run_bytecode(
s.L,
(*C.uchar)(unsafe.Pointer(&bytecode[0])),
C.size_t(len(bytecode)),
cname,
C.int(nresults),
)
if status != 0 {
err := &LuaError{
Code: int(status),
Message: s.ToString(-1),
}
s.Pop(1) // Remove error message
return err return err
} }
return s.RunBytecodeWithResults(nresults)
return nil
} }
// CompileAndRun compiles and immediately executes Lua code // CompileAndRun compiles and immediately executes Lua code

View File

@ -11,13 +11,7 @@ package luajit
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
// Optimized helpers for common operations // Direct execution helpers to minimize CGO transitions
static int get_abs_index(lua_State *L, int idx) {
if (idx > 0 || idx <= LUA_REGISTRYINDEX) return idx;
return lua_gettop(L) + idx + 1;
}
// Combined load and execute with no results
static int do_string(lua_State *L, const char *s) { static int do_string(lua_State *L, const char *s) {
int status = luaL_loadstring(L, s); int status = luaL_loadstring(L, s);
if (status == 0) { if (status == 0) {
@ -26,7 +20,6 @@ static int do_string(lua_State *L, const char *s) {
return status; return status;
} }
// Combined load and execute file
static int do_file(lua_State *L, const char *filename) { static int do_file(lua_State *L, const char *filename) {
int status = luaL_loadfile(L, filename); int status = luaL_loadfile(L, filename);
if (status == 0) { if (status == 0) {
@ -34,6 +27,12 @@ static int do_file(lua_State *L, const char *filename) {
} }
return status; return status;
} }
static int execute_with_results(lua_State *L, const char *code, int store_results) {
int status = luaL_loadstring(L, code);
if (status != 0) return status;
return lua_pcall(L, 0, store_results ? LUA_MULTRET : 0, 0);
}
*/ */
import "C" import "C"
import ( import (
@ -339,9 +338,16 @@ func (s *State) LoadString(code string) error {
ccode := C.CString(code) ccode := C.CString(code)
defer C.free(unsafe.Pointer(ccode)) defer C.free(unsafe.Pointer(ccode))
return s.safeCall(func() C.int { status := C.luaL_loadstring(s.L, ccode)
return C.luaL_loadstring(s.L, ccode) if status != 0 {
}) err := &LuaError{
Code: int(status),
Message: s.ToString(-1),
}
s.Pop(1) // Remove error message
return err
}
return nil
} }
// LoadFile loads a Lua chunk from a file without executing it // LoadFile loads a Lua chunk from a file without executing it
@ -349,16 +355,30 @@ func (s *State) LoadFile(filename string) error {
cfilename := C.CString(filename) cfilename := C.CString(filename)
defer C.free(unsafe.Pointer(cfilename)) defer C.free(unsafe.Pointer(cfilename))
return s.safeCall(func() C.int { status := C.luaL_loadfile(s.L, cfilename)
return C.luaL_loadfile(s.L, cfilename) if status != 0 {
}) err := &LuaError{
Code: int(status),
Message: s.ToString(-1),
}
s.Pop(1) // Remove error message
return err
}
return nil
} }
// Call calls a function with the given number of arguments and results // Call calls a function with the given number of arguments and results
func (s *State) Call(nargs, nresults int) error { func (s *State) Call(nargs, nresults int) error {
return s.safeCall(func() C.int { status := C.lua_pcall(s.L, C.int(nargs), C.int(nresults), 0)
return C.lua_pcall(s.L, C.int(nargs), C.int(nresults), 0) if status != 0 {
}) err := &LuaError{
Code: int(status),
Message: s.ToString(-1),
}
s.Pop(1) // Remove error message
return err
}
return nil
} }
// DoString executes a Lua string and cleans up the stack // DoString executes a Lua string and cleans up the stack
@ -366,9 +386,16 @@ func (s *State) DoString(code string) error {
ccode := C.CString(code) ccode := C.CString(code)
defer C.free(unsafe.Pointer(ccode)) defer C.free(unsafe.Pointer(ccode))
return s.safeCall(func() C.int { status := C.do_string(s.L, ccode)
return C.do_string(s.L, ccode) if status != 0 {
}) err := &LuaError{
Code: int(status),
Message: s.ToString(-1),
}
s.Pop(1) // Remove error message
return err
}
return nil
} }
// DoFile executes a Lua file and cleans up the stack // DoFile executes a Lua file and cleans up the stack
@ -376,9 +403,16 @@ func (s *State) DoFile(filename string) error {
cfilename := C.CString(filename) cfilename := C.CString(filename)
defer C.free(unsafe.Pointer(cfilename)) defer C.free(unsafe.Pointer(cfilename))
return s.safeCall(func() C.int { status := C.do_file(s.L, cfilename)
return C.do_file(s.L, cfilename) if status != 0 {
}) err := &LuaError{
Code: int(status),
Message: s.ToString(-1),
}
s.Pop(1) // Remove error message
return err
}
return nil
} }
// Execute executes a Lua string and returns the number of results left on the stack // Execute executes a Lua string and returns the number of results left on the stack
@ -388,25 +422,17 @@ func (s *State) Execute(code string) (int, error) {
ccode := C.CString(code) ccode := C.CString(code)
defer C.free(unsafe.Pointer(ccode)) defer C.free(unsafe.Pointer(ccode))
var nresults int status := C.execute_with_results(s.L, ccode, 1) // store_results=true
err := s.safeCall(func() C.int { if status != 0 {
status := C.luaL_loadstring(s.L, ccode) err := &LuaError{
if status != 0 { Code: int(status),
return status Message: s.ToString(-1),
} }
s.Pop(1) // Remove error message
status = C.lua_pcall(s.L, 0, C.LUA_MULTRET, 0)
if status == 0 {
nresults = s.GetTop() - baseTop
}
return status
})
if err != nil {
return 0, err return 0, err
} }
return nresults, nil return s.GetTop() - baseTop, nil
} }
// ExecuteWithResult executes a Lua string and returns the first result // ExecuteWithResult executes a Lua string and returns the first result
@ -426,6 +452,20 @@ func (s *State) ExecuteWithResult(code string) (any, error) {
return s.ToValue(-nresults) return s.ToValue(-nresults)
} }
// BatchExecute executes multiple statements with a single CGO transition
func (s *State) BatchExecute(statements []string) error {
// Join statements with semicolons
combinedCode := ""
for i, stmt := range statements {
combinedCode += stmt
if i < len(statements)-1 {
combinedCode += "; "
}
}
return s.DoString(combinedCode)
}
// Package path operations // Package path operations
// SetPackagePath sets the Lua package.path // SetPackagePath sets the Lua package.path
@ -441,36 +481,3 @@ func (s *State) AddPackagePath(path string) error {
code := fmt.Sprintf(`package.path = package.path .. ";%s"`, path) code := fmt.Sprintf(`package.path = package.path .. ";%s"`, path)
return s.DoString(code) return s.DoString(code)
} }
// Helper functions
// checkStack ensures there is enough space on the Lua stack
func (s *State) checkStack(n int) error {
if C.lua_checkstack(s.L, C.int(n)) == 0 {
return fmt.Errorf("stack overflow (cannot allocate %d slots)", n)
}
return nil
}
// safeCall wraps a potentially dangerous C call with stack checking
func (s *State) safeCall(f func() C.int) error {
// Ensure we have enough stack space
if err := s.checkStack(LUA_MINSTACK); err != nil {
return err
}
// Make the call
status := f()
// Check for errors
if status != 0 {
err := &LuaError{
Code: int(status),
Message: s.ToString(-1),
}
s.Pop(1) // Remove error message
return err
}
return nil
}