diff --git a/guest/api/types.go b/guest/api/types.go index 1e5579b1..26accba4 100644 --- a/guest/api/types.go +++ b/guest/api/types.go @@ -67,6 +67,13 @@ type FilterPlugin interface { Filter(state CycleState, pod proto.Pod, nodeInfo NodeInfo) *Status } +// PostFilterPlugin is a WebAssembly implementation of framework.PostFilterPlugin. +type PostFilterPlugin interface { + Plugin + + PostFilter(state CycleState, pod proto.Pod, filteredNodeStatusMap NodeToStatusMap) (nominatedNodeName string, nominatingMode int32, status *Status) +} + // EnqueueExtensions is a WebAssembly implementation of framework.EnqueueExtensions. type EnqueueExtensions interface { EventsToRegister() []ClusterEvent @@ -97,3 +104,5 @@ type NodeInfo interface { Node() proto.Node } + +type NodeToStatusMap map[string]*Status diff --git a/guest/plugin/plugin.go b/guest/plugin/plugin.go index 15c668b7..5b553785 100644 --- a/guest/plugin/plugin.go +++ b/guest/plugin/plugin.go @@ -20,6 +20,7 @@ import ( "sigs.k8s.io/kube-scheduler-wasm-extension/guest/api" "sigs.k8s.io/kube-scheduler-wasm-extension/guest/enqueue" "sigs.k8s.io/kube-scheduler-wasm-extension/guest/filter" + "sigs.k8s.io/kube-scheduler-wasm-extension/guest/internal/postfilter" "sigs.k8s.io/kube-scheduler-wasm-extension/guest/internal/prefilter" "sigs.k8s.io/kube-scheduler-wasm-extension/guest/prescore" "sigs.k8s.io/kube-scheduler-wasm-extension/guest/score" @@ -51,6 +52,9 @@ func Set(plugin api.Plugin) { if plugin, ok := plugin.(api.FilterPlugin); ok { filter.SetPlugin(plugin) } + if plugin, ok := plugin.(api.PostFilterPlugin); ok { + postfilter.SetPlugin(plugin) + } if plugin, ok := plugin.(api.PreScorePlugin); ok { prescore.SetPlugin(plugin) } diff --git a/guest/postfilter/imports.go b/guest/postfilter/imports.go new file mode 100644 index 00000000..7fa755d1 --- /dev/null +++ b/guest/postfilter/imports.go @@ -0,0 +1,22 @@ +//go:build tinygo.wasm + +/* + Copyright 2023 The Kubernetes Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package postfilter + +//go:wasmimport k8s.io/scheduler result.nominated_node_name +func setNominatedNodeNameResult(ptr, size uint32) diff --git a/guest/postfilter/imports_stub.go b/guest/postfilter/imports_stub.go new file mode 100644 index 00000000..83e49d80 --- /dev/null +++ b/guest/postfilter/imports_stub.go @@ -0,0 +1,22 @@ +//go:build !tinygo.wasm + +/* + Copyright 2023 The Kubernetes Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package postfilter + +// setNominatedNodeNameResult is stubbed for compilation outside TinyGo. +func setNominatedNodeNameResult(uint32, uint32) {} diff --git a/guest/postfilter/postfilter.go b/guest/postfilter/postfilter.go new file mode 100644 index 00000000..8c9b0c3a --- /dev/null +++ b/guest/postfilter/postfilter.go @@ -0,0 +1,87 @@ +/* + Copyright 2023 The Kubernetes Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Package postfilter exports an api.PostFilterPlugin to the host. +package postfilter + +import ( + "runtime" + "unsafe" + + "sigs.k8s.io/kube-scheduler-wasm-extension/guest/api" +) + +// postfilter is the current plugin assigned with SetPlugin. +var postfilter api.PostFilterPlugin + +// SetPlugin should be called in `main` to assign an api.PostFilterPlugin +// instance. +// +// For example: +// +// func main() { +// plugin := filterPlugin{} +// postfilter.SetPlugin(plugin) +// filter.SetPlugin(plugin) +// } +// +// type filterPlugin struct{} +// +// func (filterPlugin) PostFilter(state api.CycleState, pod proto.Pod, filteredNodeStatusMap internalpostfilter.NodeToStatusMap) (int32, status *api.Status) { +// // Write state you need on Filter +// } +// +// func (filterPlugin) Filter(state api.CycleState, pod api.Pod, nodeInfo api.NodeInfo) (status *api.Status) { +// var Filter int32 +// // Derive Filter for the node name using state set on PreFilter! +// return Filter, nil +// } +func SetPlugin(postfilterPlugin api.PostFilterPlugin) { + if postfilterPlugin == nil { + panic("nil postfilterPlugin") + } + postfilter = postfilterPlugin + plugin.MustSet(postfilterPlugin) +} + +// prevent unused lint errors (lint is run with normal go). +var _ func() uint64 = _postfilter + +// _postfilter is only exported to the host. +// +//export postfilter +func _postfilter() uint64 { //nolint + + if postfilter == nil { // Then, the user didn't define one. + // Unlike most plugins we always export postfilter so that we can reset + // the cycle state: return success to avoid no-op overhead. + return 0 + } + + // The parameters passed are lazy with regard to host functions. This means + // a no-op plugin should not have any unmarshal penalty. + nominatedNodeName, nominatingMode, status := postfilter.PostFilter(CycleState, Pod, nil) + + cString := []byte(nominatedNodeName) + if cString != nil { + ptr := uint32(uintptr(unsafe.Pointer(&cString[0]))) + size := uint32(len(cString)) + setNominatedNodeNameResult(ptr, size) + runtime.KeepAlive(cString) // until ptr is no longer needed. + } + + return (uint64(nominatingMode) << uint64(32)) | uint64(imports.StatusToCode(status)) +} diff --git a/guest/prefilter/prefilter.go b/guest/prefilter/prefilter.go index 73db7179..26745fa0 100644 --- a/guest/prefilter/prefilter.go +++ b/guest/prefilter/prefilter.go @@ -35,7 +35,7 @@ import ( // // type filterPlugin struct{} // -// func (filterPlugin) PreFilter(state api.CycleState, pod proto.Pod, nodeList proto.NodeList) { +// func (filterPlugin) PreFilter(state api.CycleState, pod proto.Pod) (nodeNames []string, status *Status) { // // Write state you need on Filter // } // diff --git a/scheduler/plugin/guest.go b/scheduler/plugin/guest.go index 37ed5b6c..1193e67e 100644 --- a/scheduler/plugin/guest.go +++ b/scheduler/plugin/guest.go @@ -28,23 +28,25 @@ import ( ) const ( - guestExportMemory = "memory" - guestExportEnqueue = "enqueue" - guestExportPreFilter = "prefilter" - guestExportFilter = "filter" - guestExportPreScore = "prescore" - guestExportScore = "score" + guestExportMemory = "memory" + guestExportEnqueue = "enqueue" + guestExportPreFilter = "prefilter" + guestExportFilter = "filter" + guestExportPostFilter = "postfilter" + guestExportPreScore = "prescore" + guestExportScore = "score" ) type guest struct { - guest wazeroapi.Module - out *bytes.Buffer - enqueueFn wazeroapi.Function - prefilterFn wazeroapi.Function - filterFn wazeroapi.Function - prescoreFn wazeroapi.Function - scoreFn wazeroapi.Function - callStack []uint64 + guest wazeroapi.Module + out *bytes.Buffer + enqueueFn wazeroapi.Function + prefilterFn wazeroapi.Function + filterFn wazeroapi.Function + postfilterFn wazeroapi.Function + prescoreFn wazeroapi.Function + scoreFn wazeroapi.Function + callStack []uint64 } func compileGuest(ctx context.Context, runtime wazero.Runtime, guestBin []byte) (guest wazero.CompiledModule, err error) { @@ -82,14 +84,15 @@ func (pl *wasmPlugin) newGuest(ctx context.Context) (*guest, error) { callStack := make([]uint64, 1) return &guest{ - guest: g, - out: &out, - enqueueFn: g.ExportedFunction(guestExportEnqueue), - prefilterFn: g.ExportedFunction(guestExportPreFilter), - filterFn: g.ExportedFunction(guestExportFilter), - prescoreFn: g.ExportedFunction(guestExportPreScore), - scoreFn: g.ExportedFunction(guestExportScore), - callStack: callStack, + guest: g, + out: &out, + enqueueFn: g.ExportedFunction(guestExportEnqueue), + prefilterFn: g.ExportedFunction(guestExportPreFilter), + filterFn: g.ExportedFunction(guestExportFilter), + postfilterFn: g.ExportedFunction(guestExportPostFilter), + prescoreFn: g.ExportedFunction(guestExportPreScore), + scoreFn: g.ExportedFunction(guestExportScore), + callStack: callStack, }, nil } @@ -131,6 +134,24 @@ func (g *guest) filter(ctx context.Context) *framework.Status { return framework.NewStatus(framework.Code(statusCode), statusReason) } +// postFilter calls guestExportPostFilter. +func (g *guest) postFilter(ctx context.Context) (*framework.PostFilterResult, *framework.Status) { + defer g.out.Reset() + callStack := g.callStack + + if err := g.postfilterFn.CallWithStack(ctx, callStack); err != nil { + return nil, framework.AsStatus(decorateError(g.out, guestExportPostFilter, err)) + } + nominatedNodeName := paramsFromContext(ctx).resultNominatedNodeName + nominatingMode := framework.NominatingMode(int32(callStack[0] >> 32)) + + statusCode := int32(callStack[0]) + statusReason := paramsFromContext(ctx).resultStatusReason + + nominatingInfo := &framework.NominatingInfo{NominatedNodeName: nominatedNodeName, NominatingMode: nominatingMode} + return &framework.PostFilterResult{NominatingInfo: nominatingInfo}, framework.NewStatus(framework.Code(statusCode), statusReason) +} + // preScore calls guestExportPreScore. func (g *guest) preScore(ctx context.Context) *framework.Status { defer g.out.Reset() @@ -188,6 +209,11 @@ func detectInterfaces(exportedFns map[string]wazeroapi.FunctionDefinition) (inte return 0, fmt.Errorf("wasm: guest exports the wrong signature for func[%s]. should be () -> (i32)", name) } e |= iFilterPlugin + case guestExportPostFilter: + if len(f.ParamTypes()) != 0 || !bytes.Equal(f.ResultTypes(), []wazeroapi.ValueType{i64}) { + return 0, fmt.Errorf("wasm: guest exports the wrong signature for func[%s]. should be () -> (i64)", name) + } + e |= iPostFilterPlugin case guestExportPreScore: if len(f.ParamTypes()) != 0 || !bytes.Equal(f.ResultTypes(), []wazeroapi.ValueType{i32}) { return 0, fmt.Errorf("wasm: guest exports the wrong signature for func[%s]. should be () -> (i32)", name) diff --git a/scheduler/plugin/host.go b/scheduler/plugin/host.go index 5befeb10..d294a99c 100644 --- a/scheduler/plugin/host.go +++ b/scheduler/plugin/host.go @@ -27,22 +27,23 @@ import ( ) const ( - i32 = wazeroapi.ValueTypeI32 - i64 = wazeroapi.ValueTypeI64 - k8sApi = "k8s.io/api" - k8sApiNode = "node" - k8sApiNodeList = "nodeList" - k8sApiNodeName = "nodeName" - k8sApiPod = "pod" - k8sKlog = "k8s.io/klog" - k8sKlogLog = "log" - k8sKlogLogs = "logs" - k8sKlogSeverity = "severity" - k8sScheduler = "k8s.io/scheduler" - k8sSchedulerGetConfig = "get_config" - k8sSchedulerResultClusterEvents = "result.cluster_events" - k8sSchedulerResultNodeNames = "result.node_names" - k8sSchedulerResultStatusReason = "result.status_reason" + i32 = wazeroapi.ValueTypeI32 + i64 = wazeroapi.ValueTypeI64 + k8sApi = "k8s.io/api" + k8sApiNode = "node" + k8sApiNodeList = "nodeList" + k8sApiNodeName = "nodeName" + k8sApiPod = "pod" + k8sKlog = "k8s.io/klog" + k8sKlogLog = "log" + k8sKlogLogs = "logs" + k8sKlogSeverity = "severity" + k8sScheduler = "k8s.io/scheduler" + k8sSchedulerGetConfig = "get_config" + k8sSchedulerResultClusterEvents = "result.cluster_events" + k8sSchedulerResultNodeNames = "result.node_names" + k8sSchedulerResultNominatedNodeName = "result.nominating_node_name" + k8sSchedulerResultStatusReason = "result.status_reason" ) func instantiateHostApi(ctx context.Context, runtime wazero.Runtime) (wazeroapi.Module, error) { @@ -90,6 +91,9 @@ func instantiateHostScheduler(ctx context.Context, runtime wazero.Runtime, guest WithGoModuleFunction(wazeroapi.GoModuleFunc(k8sSchedulerResultNodeNamesFn), []wazeroapi.ValueType{i32, i32}, []wazeroapi.ValueType{}). WithParameterNames("buf", "buf_len").Export(k8sSchedulerResultNodeNames). NewFunctionBuilder(). + WithGoModuleFunction(wazeroapi.GoModuleFunc(k8sSchedulerResultNominatedNodeNameFn), []wazeroapi.ValueType{i32, i32}, []wazeroapi.ValueType{}). + WithParameterNames("buf", "buf_len").Export(k8sSchedulerResultNominatedNodeName). + NewFunctionBuilder(). WithGoModuleFunction(wazeroapi.GoModuleFunc(k8sSchedulerResultStatusReasonFn), []wazeroapi.ValueType{i32, i32}, []wazeroapi.ValueType{}). WithParameterNames("buf", "buf_len").Export(k8sSchedulerResultStatusReason). Instantiate(ctx) @@ -128,6 +132,9 @@ type stack struct { // resultNodeNames is returned by guest.prefilterFn resultNodeNames []string + // resultNominatedNodeName is returned by guest.postfilterFn + resultNominatedNodeName string + // reason returned by all guest exports except guest.enqueueFn // // It is a field to avoid compiler-specific malloc/free functions, and to @@ -303,6 +310,21 @@ func k8sSchedulerResultNodeNamesFn(ctx context.Context, mod wazeroapi.Module, st paramsFromContext(ctx).resultNodeNames = nodeNames } +// k8sSchedulerResultNominatedNodeNameFn is a function used by the wasm guest to set the +// nominated node name result from guestExportPostFilter. +func k8sSchedulerResultNominatedNodeNameFn(ctx context.Context, mod wazeroapi.Module, stack []uint64) { + buf := uint32(stack[0]) + bufLen := uint32(stack[1]) + + var nominatedNodeName string + if b, ok := mod.Memory().Read(buf, bufLen); !ok { + panic("out of memory reading nominatedNodeName") + } else { + nominatedNodeName = string(b) + } + paramsFromContext(ctx).resultNominatedNodeName = nominatedNodeName +} + // k8sSchedulerResultStatusReasonFn is a function used by the wasm guest to set the // framework.Status reason result from all functions. func k8sSchedulerResultStatusReasonFn(ctx context.Context, mod wazeroapi.Module, stack []uint64) { diff --git a/scheduler/plugin/mask.go b/scheduler/plugin/mask.go index 17318b7c..1a53acbc 100644 --- a/scheduler/plugin/mask.go +++ b/scheduler/plugin/mask.go @@ -49,6 +49,7 @@ func maskInterfaces(plugin *wasmPlugin) (framework.Plugin, error) { iPreBindPlugin | iPostBindPlugin) + // TODO: ask about PostFilter switch i { case iFilterPlugin: type filter interface { diff --git a/scheduler/plugin/plugin.go b/scheduler/plugin/plugin.go index c87f8ade..12198411 100644 --- a/scheduler/plugin/plugin.go +++ b/scheduler/plugin/plugin.go @@ -267,13 +267,22 @@ func (pl *wasmPlugin) Filter(ctx context.Context, _ *framework.CycleState, pod * var _ framework.PostFilterPlugin = (*wasmPlugin)(nil) // PostFilter implements the same method as documented on framework.PostFilterPlugin. -func (pl *wasmPlugin) PostFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, filteredNodeStatusMap framework.NodeToStatusMap) (*framework.PostFilterResult, *framework.Status) { +func (pl *wasmPlugin) PostFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, filteredNodeStatusMap framework.NodeToStatusMap) (result *framework.PostFilterResult, status *framework.Status) { // We implement PostFilterPlugin with FilterPlugin, even when the guest doesn't. if pl.guestInterfaces&iPostFilterPlugin == 0 { return nil, nil // unimplemented } - panic("TODO: scheduling: PostFilter") + // Add the stack to the go context so that the corresponding host function + // can look them up. + params := &stack{pod: pod} + ctx = context.WithValue(ctx, stackKey{}, params) + if err := pl.pool.doWithSchedulingGuest(ctx, pod.UID, func(g *guest) { + result, status = g.postFilter(ctx) + }); err != nil { + status = framework.AsStatus(err) + } + return } var _ framework.PreScorePlugin = (*wasmPlugin)(nil) diff --git a/scheduler/plugin/plugin_test.go b/scheduler/plugin/plugin_test.go index 5be9886d..1aa9b231 100644 --- a/scheduler/plugin/plugin_test.go +++ b/scheduler/plugin/plugin_test.go @@ -561,6 +561,117 @@ wasm stack trace: } } +func TestPostFilter(t *testing.T) { + tests := []struct { + name string + guestURL string + args []string + globals map[string]int32 + pod *v1.Pod + nodeName string + expectedResult *framework.PostFilterResult + expectedStatusCode framework.Code + expectedStatusMessage string + }{ + { + name: "scored: nodeName equals spec.NodeName", + args: []string{"test", "score"}, + pod: test.PodSmall, + nodeName: test.PodSmall.Spec.NodeName, + expectedStatusCode: framework.Success, + }, + { + name: "skipped: bad-node", + args: []string{"test", "score"}, + pod: test.PodSmall, + nodeName: "bad-node", + expectedStatusCode: framework.Success, + }, + { + name: "most negative score", + guestURL: test.URLTestPostFilterFromGlobal, + pod: test.PodSmall, + nodeName: test.NodeSmall.Name, + globals: map[string]int32{"score": math.MinInt32}, + expectedStatusCode: framework.Success, + }, + { + name: "min score", + guestURL: test.URLTestPostFilterFromGlobal, + pod: test.PodSmall, + nodeName: test.NodeSmall.Name, + globals: map[string]int32{"score": math.MinInt32}, + expectedStatusCode: framework.Success, + }, + { + name: "max score", + guestURL: test.URLTestPostFilterFromGlobal, + pod: test.PodSmall, + nodeName: test.NodeSmall.Name, + globals: map[string]int32{"score": math.MaxInt32}, + expectedStatusCode: framework.Success, + }, + { + name: "min statusCode", + guestURL: test.URLTestPostFilterFromGlobal, + pod: test.PodSmall, + nodeName: test.NodeSmall.Name, + globals: map[string]int32{"status_code": math.MinInt32}, + expectedStatusCode: math.MinInt32, + }, + { + name: "max statusCode", + guestURL: test.URLTestPostFilterFromGlobal, + pod: test.PodSmall, + nodeName: test.NodeSmall.Name, + globals: map[string]int32{"status_code": math.MaxInt32}, + expectedStatusCode: math.MaxInt32, + }, + { + name: "panic", + guestURL: test.URLErrorPanicOnPostFilter, + pod: test.PodSmall, + nodeName: test.NodeSmall.Name, + expectedStatusCode: framework.Error, + expectedStatusMessage: `wasm: score error: panic! +wasm error: unreachable +wasm stack trace: + panic_on_score.$1() i64`, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + guestURL := tc.guestURL + if guestURL == "" { + guestURL = test.URLTestPostFilterFromGlobal + } + + p, err := wasm.NewFromConfig(ctx, wasm.PluginConfig{GuestURL: guestURL, Args: tc.args}) + if err != nil { + t.Fatal(err) + } + defer p.(io.Closer).Close() + + if len(tc.globals) > 0 { + pl := wasm.NewTestWasmPlugin(p) + pl.SetGlobals(tc.globals) + } + + result, status := p.(framework.PostFilterPlugin).PostFilter(ctx, nil, tc.pod, nil) + if want, have := tc.expectedResult, result; want != have { + t.Fatalf("unexpected score: want %v, have %v", want, have) + } + if want, have := tc.expectedStatusCode, status.Code(); want != have { + t.Fatalf("unexpected status code: want %v, have %v", want, have) + } + if want, have := tc.expectedStatusMessage, status.Message(); want != have { + t.Fatalf("unexpected status message: want %v, have %v", want, have) + } + }) + } +} + func TestPreScore(t *testing.T) { tests := []struct { name string diff --git a/scheduler/test/testdata.go b/scheduler/test/testdata.go index 52219ca3..46901c84 100644 --- a/scheduler/test/testdata.go +++ b/scheduler/test/testdata.go @@ -26,6 +26,8 @@ var URLErrorPanicOnPreFilter = localURL(pathWatError("panic_on_prefilter")) var URLErrorPanicOnFilter = localURL(pathWatError("panic_on_filter")) +var URLErrorPanicOnPostFilter = localURL(pathWatError("panic_on_postfilter")) + var URLErrorPanicOnPreScore = localURL(pathWatError("panic_on_prescore")) var URLErrorPreScoreWithoutScore = localURL(pathWatError("prescore_without_score")) @@ -48,6 +50,8 @@ var URLTestFilter = localURL(pathTinyGoTest("filter")) var URLTestFilterFromGlobal = localURL(pathWatTest("filter_from_global")) +var URLTestPostFilterFromGlobal = localURL(pathWatTest("postfilter_from_global")) + var URLTestPreScoreFromGlobal = localURL(pathWatTest("prescore_from_global")) var URLTestScore = localURL(pathTinyGoTest("score")) diff --git a/scheduler/test/testdata/error/panic_on_postfilter.wasm b/scheduler/test/testdata/error/panic_on_postfilter.wasm new file mode 100644 index 00000000..fd0b614e Binary files /dev/null and b/scheduler/test/testdata/error/panic_on_postfilter.wasm differ diff --git a/scheduler/test/testdata/error/panic_on_postfilter.wat b/scheduler/test/testdata/error/panic_on_postfilter.wat new file mode 100644 index 00000000..55d2cdbc --- /dev/null +++ b/scheduler/test/testdata/error/panic_on_postfilter.wat @@ -0,0 +1,29 @@ +;; panic_on_postfilter is a postfilter which issues an unreachable instruction +;; after writing an error to stdout. This simulates a panic in TinyGo. +(module $panic_on_postfilter + ;; Import the fd_write function from wasi, used in TinyGo for println. + (import "wasi_snapshot_preview1" "fd_write" + (func $wasi.fd_write (param $fd i32) (param $iovs i32) (param $iovs_len i32) (param $result.size i32) (result (;errno;) i32))) + + ;; Allocate the minimum amount of memory, 1 page (64KB). + (memory (export "memory") 1 1) + + ;; Pre-populate memory with the panic message, in iovec format + (data (i32.const 0) "\08") ;; iovs[0].offset + (data (i32.const 4) "\06") ;; iovs[0].length + (data (i32.const 8) "panic!") ;; iovs[0] + + ;; On postfilter, write "panic!" to stdout and crash. + (func (export "postfilter") (result i64) + ;; Write the panic to stdout via its iovec [offset, len]. + (call $wasi.fd_write + (i32.const 1) ;; stdout + (i32.const 0) ;; where's the iovec + (i32.const 1) ;; only one iovec + (i32.const 0) ;; overwrite the iovec with the ignored result. + ) + drop ;; ignore the errno returned + + ;; Issue the unreachable instruction instead of returning a code + (unreachable)) +) diff --git a/scheduler/test/testdata/test/all_noop.wasm b/scheduler/test/testdata/test/all_noop.wasm index 151c227e..50dfa3f2 100644 Binary files a/scheduler/test/testdata/test/all_noop.wasm and b/scheduler/test/testdata/test/all_noop.wasm differ diff --git a/scheduler/test/testdata/test/all_noop.wat b/scheduler/test/testdata/test/all_noop.wat index d505c42a..7c80ae68 100644 --- a/scheduler/test/testdata/test/all_noop.wat +++ b/scheduler/test/testdata/test/all_noop.wat @@ -6,5 +6,6 @@ (func (export "prefilter") (result i32) (return (i32.const 0))) (func (export "filter") (result i32) (return (i32.const 0))) + (func (export "postfilter") (result i32) (return (i32.const 0))) (func (export "score") (result i64) (return (i64.const 0))) ) diff --git a/scheduler/test/testdata/test/postfilter_from_global.wasm b/scheduler/test/testdata/test/postfilter_from_global.wasm new file mode 100644 index 00000000..1a9fd4f1 Binary files /dev/null and b/scheduler/test/testdata/test/postfilter_from_global.wasm differ diff --git a/scheduler/test/testdata/test/postfilter_from_global.wat b/scheduler/test/testdata/test/postfilter_from_global.wat new file mode 100644 index 00000000..b0be3cf0 --- /dev/null +++ b/scheduler/test/testdata/test/postfilter_from_global.wat @@ -0,0 +1,30 @@ +;; postfilter_from_global lets us test the value range of nominating_mode and status_code +(module $postfilter_from_global + + ;; Allocate the minimum amount of memory, 1 page (64KB). + (memory (export "memory") 1 1) + + ;; nominating_mode is set by the host. + (global $nominating_mode (export "nominating_mode_global") (mut i32) (i32.const 0)) + ;; status_code is set by the host. + (global $status_code (export "status_code_global") (mut i32) (i32.const 0)) + + (func (export "postfilter") (result i64) + ;; var nominating_mode int32 + (local $nominating_mode i32) + + ;; var status_code int32 + (local $status_code i32) + + ;; nominating_mode = global.nominating_mode + (local.set $nominating_mode (global.get $nominating_mode)) + + ;; status_code = global.status_code + (local.set $status_code (global.get $status_code)) + + ;; return uint64(nominating_mode) << 32 | uint64(status_code) + (return + (i64.or + (i64.shl (i64.extend_i32_u (local.get $nominating_mode)) (i64.const 32)) + (i64.extend_i32_u (local.get $status_code))))) +)