Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Implement wasi-http (requests) for wazero #1519

Closed
wants to merge 3 commits into from
Closed
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
6 changes: 6 additions & 0 deletions cmd/wazero/wazero.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ import (
"github.com/tetratelabs/wazero/experimental/gojs"
"github.com/tetratelabs/wazero/experimental/logging"
"github.com/tetratelabs/wazero/experimental/sock"
"github.com/tetratelabs/wazero/imports/default_http"
"github.com/tetratelabs/wazero/imports/wasi_http"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
"github.com/tetratelabs/wazero/imports/wasi_streams"
"github.com/tetratelabs/wazero/internal/platform"
"github.com/tetratelabs/wazero/internal/version"
"github.com/tetratelabs/wazero/sys"
Expand Down Expand Up @@ -324,6 +327,9 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer) int {
switch detectImports(code.ImportedFunctions()) {
case modeWasi:
wasi_snapshot_preview1.MustInstantiate(ctx, rt)
wasi_streams.MustInstantiate(ctx, rt)
wasi_http.MustInstantiate(ctx, rt)
default_http.MustInstantiate(ctx, rt)
_, err = rt.InstantiateModule(ctx, code, conf)
case modeWasiUnstable:
// Instantiate the current WASI functions under the wasi_unstable
Expand Down
181 changes: 181 additions & 0 deletions imports/default_http/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package default_http

import (
"context"
"encoding/binary"

"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/wasip1"
"github.com/tetratelabs/wazero/internal/wasm"
)

// ModuleName is the module name WASI functions are exported into.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This link seems misleading, should we point at a place where the default-outgoing-HTTP module is described?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, sorry that is a cut/paste left over. I stole a bunch of this code from the wasi implementation :)

const ModuleName = "default-outgoing-HTTP"

const i32, i64 = wasm.ValueTypeI32, wasm.ValueTypeI64

var le = binary.LittleEndian

// MustInstantiate calls Instantiate or panics on error.
//
// This is a simpler function for those who know the module ModuleName is not
// already instantiated, and don't need to unload it.
func MustInstantiate(ctx context.Context, r wazero.Runtime) {
if _, err := Instantiate(ctx, r); err != nil {
panic(err)
}
}

// Instantiate instantiates the ModuleName module into the runtime.
//
// # Notes
//
// - Failure cases are documented on wazero.Runtime InstantiateModule.
// - Closing the wazero.Runtime has the same effect as closing the result.
func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) {
return NewBuilder(r).Instantiate(ctx)
}

// Builder configures the ModuleName module for later use via Compile or Instantiate.
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
type Builder interface {
// Compile compiles the ModuleName module. Call this before Instantiate.
//
// Note: This has the same effect as the same function on wazero.HostModuleBuilder.
Compile(context.Context) (wazero.CompiledModule, error)

// Instantiate instantiates the ModuleName module and returns a function to close it.
//
// Note: This has the same effect as the same function on wazero.HostModuleBuilder.
Instantiate(context.Context) (api.Closer, error)
}

// NewBuilder returns a new Builder.
func NewBuilder(r wazero.Runtime) Builder {
return &builder{r}
}

type builder struct{ r wazero.Runtime }

// hostModuleBuilder returns a new wazero.HostModuleBuilder for ModuleName
func (b *builder) hostModuleBuilder() wazero.HostModuleBuilder {
ret := b.r.NewHostModuleBuilder(ModuleName)
exportFunctions(ret)
return ret
}

// Compile implements Builder.Compile
func (b *builder) Compile(ctx context.Context) (wazero.CompiledModule, error) {
return b.hostModuleBuilder().Compile(ctx)
}

// Instantiate implements Builder.Instantiate
func (b *builder) Instantiate(ctx context.Context) (api.Closer, error) {
return b.hostModuleBuilder().Instantiate(ctx)
}

// FunctionExporter exports functions into a wazero.HostModuleBuilder.
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
type FunctionExporter interface {
ExportFunctions(wazero.HostModuleBuilder)
}

func NewFunctionExporter() FunctionExporter {
return &functionExporter{}
}

type functionExporter struct{}

// ExportFunctions implements FunctionExporter.ExportFunctions
func (functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) {
exportFunctions(builder)
}

func exportFunctions(builder wazero.HostModuleBuilder) {
exporter := builder.(wasm.HostFuncExporter)

exporter.ExportHostFunc(request)
exporter.ExportHostFunc(handle)
}

func newHostFunc(
name string,
goFunc wasiFunc,
paramTypes []api.ValueType,
paramNames ...string,
) *wasm.HostFunc {
return &wasm.HostFunc{
ExportName: name,
Name: name,
ParamTypes: paramTypes,
ParamNames: paramNames,
ResultTypes: []api.ValueType{i32},
ResultNames: []string{"errno"},
Code: wasm.Code{GoFunc: goFunc},
}
}

func newHostMethod(
name string,
goFunc wasiMethod,
paramTypes []api.ValueType,
paramNames ...string,
) *wasm.HostFunc {
return &wasm.HostFunc{
ExportName: name,
Name: name,
ParamTypes: paramTypes,
ParamNames: paramNames,
ResultTypes: []api.ValueType{},
ResultNames: []string{},
Code: wasm.Code{GoFunc: goFunc},
}
}

// wasiFunc special cases that all WASI functions return a single Errno
// result. The returned value will be written back to the stack at index zero.
type wasiFunc func(ctx context.Context, mod api.Module, params []uint64) int32

// Call implements the same method as documented on api.GoModuleFunction.
func (f wasiFunc) Call(ctx context.Context, mod api.Module, stack []uint64) {
// Write the result back onto the stack
errno := f(ctx, mod, stack)
if errno != 0 {
stack[0] = uint64(errno)
} else { // special case ass ErrnoSuccess is zero
stack[0] = 0
}
}

type wasiMethod func(ctx context.Context, mod api.Module, params []uint64)

func (f wasiMethod) Call(ctx context.Context, mod api.Module, stack []uint64) {
// Write the result back onto the stack
f(ctx, mod, stack)
}

// stubFunction stubs for GrainLang per #271.
func stubFunction(name string, paramTypes []wasm.ValueType, paramNames ...string) *wasm.HostFunc {
return &wasm.HostFunc{
ExportName: name,
Name: name,
ParamTypes: paramTypes,
ParamNames: paramNames,
ResultTypes: []api.ValueType{i32},
ResultNames: []string{"errno"},
Code: wasm.Code{
GoFunc: api.GoModuleFunc(func(_ context.Context, _ api.Module, stack []uint64) { stack[0] = uint64(wasip1.ErrnoNosys) }),
},
}
}
31 changes: 31 additions & 0 deletions imports/default_http/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package default_http

import (
"context"
"log"

"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/imports/wasi_http"
)

var request = newHostFunc("request", requestFn,
[]api.ValueType{i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32},
"a", "b", "c", "d", "e", "f", "g", "h", "j", "k", "l", "m", "n", "o")

func requestFn(_ context.Context, mod api.Module, params []uint64) int32 {
return 0
}

var handle = newHostFunc("handle", handleFn,
[]api.ValueType{i32, i32, i32, i32, i32, i32, i32, i32},
"a", "b", "c", "d", "e", "f", "g", "h")

func handleFn(_ context.Context, mod api.Module, params []uint64) int32 {
r, err := wasi_http.MakeRequest()
if err != nil {
log.Printf(err.Error())
return 0
}
wasi_http.SetResponse(r)
return 1
}
Loading