diff --git a/internal/ext/deleteme.go b/internal/ext/deleteme.go new file mode 100644 index 00000000000..a7c450d7184 --- /dev/null +++ b/internal/ext/deleteme.go @@ -0,0 +1,69 @@ +package ext + +import ( + "context" + "fmt" + "os" + "sync" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental" +) + +func deleteMe(name string, wasm []byte, needsQuickJSProvider bool) func(ctx context.Context, r wazero.Runtime, inouts []*inOut) (func() error, error) { + return func(ctx context.Context, r wazero.Runtime, inouts []*inOut) (func() error, error) { + compiledModule, err := r.CompileModule(ctx, wasm) + if err != nil { + return nil, err + } + + var compiledQuickJS wazero.CompiledModule + if needsQuickJSProvider { + compiledQuickJS, err = r.CompileModule(ctx, quickjsWasm) + if err != nil { + return nil, err + } + } + + return func() error { + var wg sync.WaitGroup + for i, c := range inouts { + name := fmt.Sprintf("%s_%d", name, i) + c := c + + wg.Add(1) + go func() { + defer wg.Done() + configBase := wazero.NewModuleConfig().WithStderr(os.Stderr).WithStartFunctions() + config := configBase.WithName(name) + if needsQuickJSProvider { + // TODO1 else, configure with stdout etc. + qmod, err := r.InstantiateModule(ctx, compiledQuickJS, configBase.WithName("").WithStdout(c.stdout).WithStdin(c.stdin)) + if err != nil { + panic(err) + } + ctx = experimental.WithImportResolver(ctx, + func(name string) api.Module { + if name == "javy_quickjs_provider_v2" { + return qmod + } + return nil + }) + + } + + mod, err := r.InstantiateModule(ctx, compiledModule, config) + if err != nil { + panic(err) + } + if _, err := mod.ExportedFunction("_start").Call(ctx); err != nil { + panic(err) + } + }() + } + wg.Wait() + return nil + }, nil + } +} diff --git a/internal/ext/katex.go b/internal/ext/katex.go index 64793f13a37..395655409bd 100644 --- a/internal/ext/katex.go +++ b/internal/ext/katex.go @@ -8,8 +8,9 @@ import ( var katexWasm []byte var katexOptions = Options{ - CompileModule: compileFunc("katex", katexWasm, true), - PoolSize: 8, + Runtime: Binary{Name: "javy_quickjs_provider_v2", Data: quickjsWasm}, + Main: Binary{Name: "renderkatex", Data: katexWasm}, + PoolSize: 8, } // StartKatex starts a new dispatcher for the Katex module. diff --git a/internal/ext/warpc.go b/internal/ext/warpc.go index ef4c6bd60f6..7d9c2400b29 100644 --- a/internal/ext/warpc.go +++ b/internal/ext/warpc.go @@ -15,6 +15,7 @@ import ( "time" "github.com/gohugoio/hugo/common/hugio" + "golang.org/x/sync/errgroup" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" @@ -216,15 +217,60 @@ func (call *call[Q, R]) done() { } } +// Binary represents a WebAssembly binary. +type Binary struct { + // The name of the binary. + // For quickjs, this must match the instance import name, "javy_quickjs_provider_v2". + // For the main module, we only use this for caching. + Name string + + // THe wasm binary. + Data []byte +} + type Options struct { Ctx context.Context - CompileModule func(ctx context.Context, r wazero.Runtime, io []*inOut) (func() error, error) + // E.g. quickjs wasm. May be omitted if not needed. + Runtime Binary + + // The main module to instantiate. + Main Binary + CompilationCacheDir string PoolSize int } +type CompileModuleContext struct { + Opts Options + Runtime wazero.Runtime +} + +type CompiledModule struct { + // Runtime (e.g. QuickJS) may be nil if not needed (e.g. embedded in Module). + Runtime wazero.CompiledModule + + // If Runtime is not nil, this should be the name of the instance. + RuntimeName string + + // The main module to instantiate. + // This will be insantiated multiple times in a pool, + // so it does not need a name. + Module wazero.CompiledModule +} + func Start[Q, R IDGetter](opts Options) (Dispatcher[Q, R], error) { + if opts.Main.Data == nil { + return nil, errors.New("Main.Data must be set") + } + if opts.Main.Name == "" { + return nil, errors.New("Main.Name must be set") + } + + if opts.Runtime.Data != nil && opts.Runtime.Name == "" { + return nil, errors.New("Runtime.Name must be set") + } + if opts.PoolSize == 0 { opts.PoolSize = 1 } @@ -242,9 +288,7 @@ func newDispatcher[Q, R IDGetter](opts Options) (*dispatcherPool[Q, R], error) { if opts.Ctx == nil { opts.Ctx = context.Background() } - if opts.CompileModule == nil { - return nil, errors.New("InstansiateModule is required") - } + ctx := opts.Ctx runtimeConfig := wazero.NewRuntimeConfig() @@ -280,14 +324,63 @@ func newDispatcher[Q, R IDGetter](opts Options) (*dispatcherPool[Q, R], error) { } } - run, err := opts.CompileModule(ctx, r, inOuts) + var ( + runtimeModule wazero.CompiledModule + mainModule wazero.CompiledModule + err error + ) + + if opts.Runtime.Data != nil { + runtimeModule, err = r.CompileModule(ctx, opts.Runtime.Data) + if err != nil { + return nil, err + } + } + + mainModule, err = r.CompileModule(ctx, opts.Main.Data) if err != nil { return nil, err } + run := func() error { + g, ctx := errgroup.WithContext(ctx) + for _, c := range inOuts { + c := c + g.Go(func() error { + configBase := wazero.NewModuleConfig().WithStdout(c.stdout).WithStdin(c.stdin).WithStartFunctions() + if opts.Runtime.Data != nil { + // This needs to be anonymous, it will be resolved in the import resolver below. + runtimeInstance, err := r.InstantiateModule(ctx, runtimeModule, configBase.WithName("")) + if err != nil { + return err + } + ctx = experimental.WithImportResolver(ctx, + func(name string) api.Module { + if name == opts.Runtime.Name { + return runtimeInstance + } + return nil + }, + ) + } + + mainInstance, err := r.InstantiateModule(ctx, mainModule, configBase.WithName("").WithStdout(c.stdout).WithStdin(c.stdin)) + if err != nil { + return err + } + if _, err := mainInstance.ExportedFunction("_start").Call(ctx); err != nil { + return err + } + + return nil + }) + } + return g.Wait() + } + done := make(chan struct{}) go func() { - // This will block until stdin is closed. + // This will block until stdin is closed or it encounters an error. err := run() if err != nil { panic(err) @@ -336,6 +429,30 @@ func printStackTrace(w io.Writer) { fmt.Fprintf(w, "%s", buf) } +// TODO1 delete me. +func compileFuncTake2(name string, wasm []byte, needsQuickJSProvider bool) func(ctx CompileModuleContext) (CompiledModule, error) { + return func(ctx CompileModuleContext) (CompiledModule, error) { + compiledModule, err := ctx.Runtime.CompileModule(ctx.Opts.Ctx, wasm) + if err != nil { + return CompiledModule{}, err + } + + var compiledQuickJS wazero.CompiledModule + if needsQuickJSProvider { + compiledQuickJS, err = ctx.Runtime.CompileModule(ctx.Opts.Ctx, quickjsWasm) + if err != nil { + return CompiledModule{}, err + } + } + + return CompiledModule{ + Runtime: compiledQuickJS, + RuntimeName: "javy_quickjs_provider_v2", + Module: compiledModule, + }, nil + } +} + func compileFunc(name string, wasm []byte, needsQuickJSProvider bool) func(ctx context.Context, r wazero.Runtime, inouts []*inOut) (func() error, error) { return func(ctx context.Context, r wazero.Runtime, inouts []*inOut) (func() error, error) { compiledModule, err := r.CompileModule(ctx, wasm) diff --git a/internal/ext/warpc_test.go b/internal/ext/warpc_test.go index 5ce28706de2..c6d9856bd81 100644 --- a/internal/ext/warpc_test.go +++ b/internal/ext/warpc_test.go @@ -23,7 +23,8 @@ func (p person) GetID() uint32 { func TestKatex(t *testing.T) { opts := Options{ - CompileModule: compileFunc("renderkatex", katexWasm, true), + Runtime: quickjsBinary, + Main: katexBinary, } d, err := Start[KatexInput, KatexOutput](opts) @@ -50,44 +51,65 @@ func TestKatex(t *testing.T) { } } -func TestGreet(t *testing.T) { - opts := Options{ - CompileModule: compileFunc("greet", greetWasm, true), - PoolSize: 2, +var ( + greetBinary = Binary{ + Name: "greet", + Data: greetWasm, } - d, err := Start[person, greeting](opts) - if err != nil { - t.Fatal(err) + katexBinary = Binary{ + Name: "renderkatex", + Data: katexWasm, } - defer d.Close() - ctx := context.Background() + quickjsBinary = Binary{ + Name: "javy_quickjs_provider_v2", + Data: quickjsWasm, + } +) - inputPerson := person{ - Name: "Person", +func TestGreet(t *testing.T) { + opts := Options{ + PoolSize: 1, + Runtime: quickjsBinary, + Main: greetBinary, } - for i := 0; i < 20; i++ { - inputPerson.ID = uint32(i + 1) - g, err := d.Execute(ctx, inputPerson) + for i := 0; i < 2; i++ { + d, err := Start[person, greeting](opts) if err != nil { t.Fatal(err) } - if g.Greeting != "Hello Person!" { - t.Fatalf("got: %v", g) - } - if g.ID != inputPerson.ID { - t.Fatalf("%d vs %d", g.ID, inputPerson.ID) + defer d.Close() + + ctx := context.Background() + + inputPerson := person{ + Name: "Person", } + for j := 0; j < 20; j++ { + inputPerson.ID = uint32(j + 1) + g, err := d.Execute(ctx, inputPerson) + if err != nil { + t.Fatal(err) + } + if g.Greeting != "Hello Person!" { + t.Fatalf("got: %v", g) + } + if g.ID != inputPerson.ID { + t.Fatalf("%d vs %d", g.ID, inputPerson.ID) + } + + } } } func TestGreetParallel(t *testing.T) { opts := Options{ - CompileModule: compileFunc("greet", greetWasm, true), - PoolSize: 4, + Runtime: quickjsBinary, + Main: greetBinary, + PoolSize: 4, } d, err := Start[*person, greeting](opts) if err != nil { @@ -135,8 +157,9 @@ func TestGreetParallel(t *testing.T) { func TestKatexParallel(t *testing.T) { opts := Options{ - CompileModule: compileFunc("katex", katexWasm, true), - PoolSize: 6, + Runtime: quickjsBinary, + Main: katexBinary, + PoolSize: 6, } d, err := Start[KatexInput, KatexOutput](opts) if err != nil { @@ -180,7 +203,8 @@ func TestKatexParallel(t *testing.T) { func BenchmarkExecuteKatex(b *testing.B) { opts := Options{ - CompileModule: compileFunc("katex", katexWasm, true), + Runtime: quickjsBinary, + Main: katexBinary, } d, err := Start[*KatexInput, KatexOutput](opts) if err != nil { @@ -209,7 +233,8 @@ func BenchmarkExecuteKatex(b *testing.B) { func BenchmarkKatexStartStop(b *testing.B) { optsTemplate := Options{ - CompileModule: compileFunc("katex", katexWasm, true), + Runtime: quickjsBinary, + Main: katexBinary, CompilationCacheDir: b.TempDir(), } @@ -246,7 +271,8 @@ var katexInputTemplate = KatexInput{ func BenchmarkExecuteKatexPara(b *testing.B) { optsTemplate := Options{ - CompileModule: compileFunc("katex", katexWasm, true), + Runtime: quickjsBinary, + Main: katexBinary, } runBench := func(b *testing.B, opts Options) { @@ -289,7 +315,8 @@ func BenchmarkExecuteKatexPara(b *testing.B) { func BenchmarkExecuteGreet(b *testing.B) { opts := Options{ - CompileModule: compileFunc("greet", greetWasm, true), + Runtime: quickjsBinary, + Main: greetBinary, } d, err := Start[*person, greeting](opts) if err != nil { @@ -320,8 +347,9 @@ func BenchmarkExecuteGreet(b *testing.B) { func BenchmarkExecuteGreetPara(b *testing.B) { opts := Options{ - CompileModule: compileFunc("greet", greetWasm, true), - PoolSize: 8, + Runtime: quickjsBinary, + Main: greetBinary, + PoolSize: 8, } d, err := Start[person, greeting](opts)