mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
wasm: introduce one-off eval function, use it instead (#3627)
* wasm/sdk: check version, call old eval path for ABI 1.1 Fixes #3146. * docs/wasm: document addition as ABI 1.2 * wasm-sdk: overwrite previous inputs, don't accumulate them There is a little room for optimization here, should the input ever grow so large that it eats up too much precious heap space, we could look into changing this so that the memory used for it can be reclaimed. * internal/compiler/wasm: commit generated wasm I've noticed that since the CI build running on macos-latest doesn't have docker installed, it cannot update these files itself at build time. We thus end up with macos binaries that have the wasm binary data from the main branch, not the PR. This can be observed from the test failure: Run make ci-binary-smoke-test-wasm BINARY=opa_darwin_amd64 chmod +x "_release/0.31.0-dev/opa_darwin_amd64" "_release/0.31.0-dev/opa_darwin_amd64" eval -t "wasm" 'time.now_ns()' make: *** [ci-binary-smoke-test-wasm] Error 2 { "errors": [ { "message": "caller not found: opa_eval (opa_eval)" } ] } Error: Process completed with exit code 2. Since I had previously commit the CSV data that drives the dead code elimination process, that optimization had failed to find a function it expected to have. Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
This commit is contained in:
@@ -3503,6 +3503,10 @@
|
||||
{
|
||||
"version": 1,
|
||||
"minor_version": 1
|
||||
},
|
||||
{
|
||||
"version": 1,
|
||||
"minor_version": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -498,7 +498,7 @@ func TestCompilerWasmTargetWithCapabilitiesMismatch(t *testing.T) {
|
||||
|
||||
for note, wabis := range map[string][]ast.WasmABIVersion{
|
||||
"none": {},
|
||||
"mismatch": {{Version: 0}, {Version: 1, Minor: 2}},
|
||||
"mismatch": {{Version: 0}, {Version: 1, Minor: 2000}},
|
||||
} {
|
||||
t.Run(note, func(t *testing.T) {
|
||||
caps := ast.CapabilitiesForThisVersion()
|
||||
|
||||
+33
-24
@@ -108,7 +108,7 @@ import functions are dependencies of the compiled policies.
|
||||
|
||||
Wasm modules built using OPA 0.27.0 onwards contain a global variable named
|
||||
`opa_wasm_abi_version` that has a constant i32 value indicating the ABI version
|
||||
this module requires. Described below you find ABI version 1.
|
||||
this module requires. Described below you find ABI versions `1.x`.
|
||||
|
||||
There's another i32 constant exported, `opa_wasm_abi_minor_version`, used
|
||||
to track backwards-compatible changes.
|
||||
@@ -129,31 +129,40 @@ Export[19]:
|
||||
|
||||
Note the `i32=1` of `global[1]`, exported by the name of `opa_wasm_abi_version`.
|
||||
|
||||
##### Version notes
|
||||
|
||||
ABI | Notes
|
||||
--- | ---
|
||||
1.0 | Start of ABI versioning.
|
||||
1.1 | Adds export `memory`.
|
||||
1.2 | Adds exported function `opa_eval`.
|
||||
|
||||
#### Exports
|
||||
|
||||
The primary exported functions for interacting with policy modules are:
|
||||
|
||||
| Function Signature | Description|
|
||||
| --- | --- |
|
||||
| <span class="opa-keep-it-together">`int32 eval(ctx_addr)`</span> | Evaluates the loaded policy with the provided evaluation context. The return value is reserved for future use. |
|
||||
| <span class="opa-keep-it-together">`value_addr builtins(void)`</span> | Returns the address of a mapping of built-in function names to numeric identifiers that are required by the policy. |
|
||||
| <span class="opa-keep-it-together">`value_addr entrypoints(void)`</span> | Returns the address of a mapping of entrypoints to numeric identifiers that can be selected when evaluating the policy. |
|
||||
| <span class="opa-keep-it-together">`ctx_addr opa_eval_ctx_new(void)`</span> | Returns the address of a newly allocated evaluation context. |
|
||||
| <span class="opa-keep-it-together">`void opa_eval_ctx_set_input(ctx_addr, value_addr)`</span> | Set the input value to use during evaluation. This must be called before each `eval()` call. If the input value is not set before evaluation, references to the `input` document result produce no results (i.e., they are undefined.) |
|
||||
| <span class="opa-keep-it-together">`void opa_eval_ctx_set_data(ctx_addr, value_addr)`</span> | Set the data value to use during evalutaion. This should be called before each `eval()` call. If the data value is not set before evalutaion, references to base `data` documents produce no results (i.e., they are undefined.) |
|
||||
| <span class="opa-keep-it-together">`void opa_eval_ctx_set_entrypoint(ctx_addr, entrypoint_id)`</span> | Set the entrypoint to evaluate. By default, entrypoint with id `0` is evaluated. |
|
||||
| <span class="opa-keep-it-together">`value_addr opa_eval_ctx_get_result(ctx_addr)`</span> | Get the result set produced by the evaluation process. |
|
||||
| <span class="opa-keep-it-together">`addr opa_malloc(int32 size)`</span> | Allocates size bytes in the shared memory and returns the starting address. |
|
||||
| <span class="opa-keep-it-together">`void opa_free(addr)`</span> | Free a pointer. Calls `opa_abort` on error. |
|
||||
| <span class="opa-keep-it-together">`value_addr opa_json_parse(str_addr, size)`</span> | Parses the JSON serialized value starting at str_addr of size bytes and returns the address of the parsed value. The parsed value may refer to a null, boolean, number, string, array, or object value. |
|
||||
| <span class="opa-keep-it-together">`value_addr opa_value_parse(str_addr, size)`</span> | The same as `opa_json_parse` except Rego set literals are supported. |
|
||||
| <span class="opa-keep-it-together">`str_addr opa_json_dump(value_addr)`</span> | Dumps the value referred to by `value_addr` to a null-terminated JSON serialized string and returns the address of the start of the string. Rego sets are serialized as JSON arrays. Non-string Rego object keys are serialized as strings. |
|
||||
| <span class="opa-keep-it-together">`str_addr opa_value_dump(value_addr)`</span> | The same as `opa_json_dump` except Rego sets are serialized using the literal syntax and non-string Rego object keys are not serialized as strings. |
|
||||
| <span class="opa-keep-it-together">`void opa_heap_ptr_set(addr)`</span> | Set the heap pointer for the next evaluation. |
|
||||
| <span class="opa-keep-it-together">`addr opa_heap_ptr_get(void)`</span> | Get the current heap pointer. |
|
||||
| <span class="opa-keep-it-together">`int32 opa_value_add_path(base_value_addr, path_value_addr, value_addr)`</span> | Add the value at the `value_addr` into the object referenced by `base_value_addr` at the given path. The `path_value_addr` must point to an array value with string keys (eg: `["a", "b", "c"]`). Existing values will be updated. On success the value at `value_addr` is no longer owned by the caller, it will be freed with the base value. The path must be freed by the caller after use (see `opa_free`). If an error occurs the base value will remain unchanged. Example: base object `{"a": {"b": 123}}`, path `["a", "x", "y"]`, and value `{"foo": "bar"}` will yield `{"a": {"b": 123, "x": {"y": {"foo": "bar"}}}}`. Returns an error code (see below). |
|
||||
| <span class="opa-keep-it-together">`int32 opa_value_remove_path(base_value_addr, path_value_addr)`</span> | Remove the value from the object referenced by `base_value_addr` at the given path. Values removed will be freed. The path must be freed by the caller after use (see `opa_free`). The `path_value_addr` must point to an array value with string keys (eg: `["a", "b", "c"]`). Returns an error code (see below). |
|
||||
The primary exported functions for interacting with policy modules are listed below.
|
||||
In the ABI column, you can find the ABI version with which the export was introduced.
|
||||
|
||||
| Function Signature | Description | ABI
|
||||
| --- | --- | --- |
|
||||
| <span class="opa-keep-it-together">`int32 eval(ctx_addr)`</span> | Evaluates the loaded policy with the provided evaluation context. The return value is reserved for future use. | 1.0 |
|
||||
| <span class="opa-keep-it-together">`value_addr builtins(void)`</span> | Returns the address of a mapping of built-in function names to numeric identifiers that are required by the policy. | 1.0 |
|
||||
| <span class="opa-keep-it-together">`value_addr entrypoints(void)`</span> | Returns the address of a mapping of entrypoints to numeric identifiers that can be selected when evaluating the policy. | 1.0 |
|
||||
| <span class="opa-keep-it-together">`ctx_addr opa_eval_ctx_new(void)`</span> | Returns the address of a newly allocated evaluation context. | 1.0 |
|
||||
| <span class="opa-keep-it-together">`void opa_eval_ctx_set_input(ctx_addr, value_addr)`</span> | Set the input value to use during evaluation. This must be called before each `eval()` call. If the input value is not set before evaluation, references to the `input` document result produce no results (i.e., they are undefined.) | 1.0 |
|
||||
| <span class="opa-keep-it-together">`void opa_eval_ctx_set_data(ctx_addr, value_addr)`</span> | Set the data value to use during evalutaion. This should be called before each `eval()` call. If the data value is not set before evalutaion, references to base `data` documents produce no results (i.e., they are undefined.) | 1.0 |
|
||||
| <span class="opa-keep-it-together">`void opa_eval_ctx_set_entrypoint(ctx_addr, entrypoint_id)`</span> | Set the entrypoint to evaluate. By default, entrypoint with id `0` is evaluated. | 1.0 |
|
||||
| <span class="opa-keep-it-together">`value_addr opa_eval_ctx_get_result(ctx_addr)`</span> | Get the result set produced by the evaluation process. | 1.0 |
|
||||
| <span class="opa-keep-it-together">`addr opa_malloc(int32 size)`</span> | Allocates size bytes in the shared memory and returns the starting address. | 1.0 |
|
||||
| <span class="opa-keep-it-together">`void opa_free(addr)`</span> | Free a pointer. Calls `opa_abort` on error. | 1.0 |
|
||||
| <span class="opa-keep-it-together">`value_addr opa_json_parse(str_addr, size)`</span> | Parses the JSON serialized value starting at str_addr of size bytes and returns the address of the parsed value. The parsed value may refer to a null, boolean, number, string, array, or object value. | 1.0 |
|
||||
| <span class="opa-keep-it-together">`value_addr opa_value_parse(str_addr, size)`</span> | The same as `opa_json_parse` except Rego set literals are supported. | 1.0 |
|
||||
| <span class="opa-keep-it-together">`str_addr opa_json_dump(value_addr)`</span> | Dumps the value referred to by `value_addr` to a null-terminated JSON serialized string and returns the address of the start of the string. Rego sets are serialized as JSON arrays. Non-string Rego object keys are serialized as strings. | 1.0 |
|
||||
| <span class="opa-keep-it-together">`str_addr opa_value_dump(value_addr)`</span> | The same as `opa_json_dump` except Rego sets are serialized using the literal syntax and non-string Rego object keys are not serialized as strings. | 1.0 |
|
||||
| <span class="opa-keep-it-together">`void opa_heap_ptr_set(addr)`</span> | Set the heap pointer for the next evaluation. | 1.0 |
|
||||
| <span class="opa-keep-it-together">`addr opa_heap_ptr_get(void)`</span> | Get the current heap pointer. | 1.0 |
|
||||
| <span class="opa-keep-it-together">`int32 opa_value_add_path(base_value_addr, path_value_addr, value_addr)`</span> | Add the value at the `value_addr` into the object referenced by `base_value_addr` at the given path. The `path_value_addr` must point to an array value with string keys (eg: `["a", "b", "c"]`). Existing values will be updated. On success the value at `value_addr` is no longer owned by the caller, it will be freed with the base value. The path must be freed by the caller after use (see `opa_free`). If an error occurs the base value will remain unchanged. Example: base object `{"a": {"b": 123}}`, path `["a", "x", "y"]`, and value `{"foo": "bar"}` will yield `{"a": {"b": 123, "x": {"y": {"foo": "bar"}}}}`. Returns an error code (see below). | 1.0 |
|
||||
| <span class="opa-keep-it-together">`int32 opa_value_remove_path(base_value_addr, path_value_addr)`</span> | Remove the value from the object referenced by `base_value_addr` at the given path. Values removed will be freed. The path must be freed by the caller after use (see `opa_free`). The `path_value_addr` must point to an array value with string keys (eg: `["a", "b", "c"]`). Returns an error code (see below). | 1.0 |
|
||||
| <span class="opa-keep-it-together">`str_addr opa_eval(addr, entrypoint_id, value_addr, str_addr, int32, addr, format)`</span> | One-off policy evaluation method. Its arguments are everything needed to evaluate: entrypoint, address of data in memory, address and length of input JSON string in memory, heap address to use, and the output format (`0` is JSON, `1` is "value", i.e. serialized Rego values). The first argument is reserved for future use and must be `0`. Returns the address to the serialised result value. | 1.2 |
|
||||
|
||||
The addresses passed and returned by the policy modules are 32-bit integer
|
||||
offsets into the shared memory region. The `value_addr` parameters and return
|
||||
@@ -162,7 +171,7 @@ values refer to OPA value data structures: `null`, `boolean`, `number`,
|
||||
|
||||
__Error codes:__
|
||||
|
||||
OPA WASM Error codes are int32 values defined as:
|
||||
OPA Wasm Error codes are int32 values defined as:
|
||||
|
||||
| Value | Name | Description |
|
||||
|-------|------|-------------|
|
||||
|
||||
@@ -200,6 +200,12 @@ opa_cmp_lt,opa_boolean
|
||||
opa_cmp_lte,opa_value_compare
|
||||
opa_cmp_lte,opa_boolean
|
||||
opa_eval_ctx_new,opa_malloc
|
||||
opa_eval,opa_abort
|
||||
opa_eval,opa_heap_ptr_set
|
||||
opa_eval,opa_value_parse
|
||||
opa_eval,eval
|
||||
opa_eval,opa_value_dump
|
||||
opa_eval,opa_json_dump
|
||||
__force_import_opa_builtins,opa_builtin0
|
||||
__force_import_opa_builtins,opa_builtin1
|
||||
__force_import_opa_builtins,opa_builtin2
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -27,7 +27,7 @@ import (
|
||||
const (
|
||||
opaWasmABIVersionVal = 1
|
||||
opaWasmABIVersionVar = "opa_wasm_abi_version"
|
||||
opaWasmABIMinorVersionVal = 1
|
||||
opaWasmABIMinorVersionVal = 2
|
||||
opaWasmABIMinorVersionVar = "opa_wasm_abi_minor_version"
|
||||
)
|
||||
|
||||
@@ -899,20 +899,7 @@ func (c *Compiler) replaceBooleanFunc() error {
|
||||
c.appendInstr(instruction.GetLocal{Index: 0})
|
||||
c.appendInstr(instruction.Select{})
|
||||
|
||||
// replace the code segment
|
||||
var idx uint32
|
||||
for _, fn := range c.module.Names.Functions {
|
||||
if fn.Name == opaBoolean {
|
||||
idx = fn.Index - uint32(c.functionImportCount())
|
||||
}
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := encoding.WriteCodeEntry(&buf, c.code); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.module.Code.Segments[idx].Code = buf.Bytes()
|
||||
return nil
|
||||
return c.storeFunc(opaBoolean, c.code)
|
||||
}
|
||||
|
||||
func (c *Compiler) compileBlock(block *ir.Block) ([]instruction.Instruction, error) {
|
||||
@@ -1487,11 +1474,17 @@ func (c *Compiler) compileExternalCall(stmt *ir.CallStmt, id int32, result *[]in
|
||||
|
||||
func (c *Compiler) emitFunctionDecl(name string, tpe module.FunctionType, export bool) {
|
||||
|
||||
typeIndex := c.emitFunctionType(tpe)
|
||||
c.module.Function.TypeIndices = append(c.module.Function.TypeIndices, typeIndex)
|
||||
c.module.Code.Segments = append(c.module.Code.Segments, module.RawCodeSegment{})
|
||||
idx := uint32((len(c.module.Function.TypeIndices) - 1) + c.functionImportCount())
|
||||
c.funcs[name] = idx
|
||||
var idx uint32
|
||||
if old, ok := c.funcs[name]; ok {
|
||||
c.debug.Printf("function declaration for %v is being emitted multiple times (overwriting old index %d)", name, old)
|
||||
idx = old
|
||||
} else {
|
||||
typeIndex := c.emitFunctionType(tpe)
|
||||
c.module.Function.TypeIndices = append(c.module.Function.TypeIndices, typeIndex)
|
||||
c.module.Code.Segments = append(c.module.Code.Segments, module.RawCodeSegment{})
|
||||
idx = uint32((len(c.module.Function.TypeIndices) - 1) + c.functionImportCount())
|
||||
c.funcs[name] = idx
|
||||
}
|
||||
|
||||
if export {
|
||||
c.module.Export.Exports = append(c.module.Export.Exports, module.Export{
|
||||
|
||||
@@ -28,6 +28,8 @@ type VM struct {
|
||||
instance *wasmtime.Instance // Pointer to avoid unintented destruction (triggering finalizers within).
|
||||
intHandle *wasmtime.InterruptHandle
|
||||
policy []byte
|
||||
abiMajorVersion int32
|
||||
abiMinorVersion int32
|
||||
memory *wasmtime.Memory
|
||||
memoryMin uint32
|
||||
memoryMax uint32
|
||||
@@ -35,6 +37,7 @@ type VM struct {
|
||||
baseHeapPtr int32
|
||||
dataAddr int32
|
||||
evalHeapPtr int32
|
||||
evalOneOff func(context.Context, int32, int32, int32, int32, int32) (int32, error)
|
||||
eval func(context.Context, int32) error
|
||||
evalCtxGetResult func(context.Context, int32) (int32, error)
|
||||
evalCtxNew func(context.Context) (int32, error)
|
||||
@@ -105,6 +108,14 @@ func newVM(opts vmOpts) (*VM, error) {
|
||||
return nil, fmt.Errorf("get interrupt handle: %w", err)
|
||||
}
|
||||
|
||||
v.abiMajorVersion, v.abiMinorVersion, err = getABIVersion(i, store)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid module: %w", err)
|
||||
}
|
||||
if v.abiMajorVersion != int32(1) || (v.abiMinorVersion != int32(1) && v.abiMinorVersion != int32(2)) {
|
||||
return nil, fmt.Errorf("invalid module: unsupported ABI version: %d.%d", v.abiMajorVersion, v.abiMinorVersion)
|
||||
}
|
||||
|
||||
v.store = store
|
||||
v.instance = i
|
||||
v.policy = opts.policy
|
||||
@@ -122,6 +133,9 @@ func newVM(opts vmOpts) (*VM, error) {
|
||||
v.evalCtxSetInput = func(ctx context.Context, a int32, b int32) error {
|
||||
return callVoid(ctx, v, "opa_eval_ctx_set_input", a, b)
|
||||
}
|
||||
v.evalOneOff = func(ctx context.Context, ep, dataAddr, inputAddr, inputLen, heapAddr int32) (int32, error) {
|
||||
return call(ctx, v, "opa_eval", 0 /* reserved */, ep, dataAddr, inputAddr, inputLen, heapAddr, 1 /* value output */)
|
||||
}
|
||||
v.evalCtxSetEntrypoint = func(ctx context.Context, a int32, b int32) error {
|
||||
return callVoid(ctx, v, "opa_eval_ctx_set_entrypoint", a, b)
|
||||
}
|
||||
@@ -238,9 +252,88 @@ func newVM(opts vmOpts) (*VM, error) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func getABIVersion(i *wasmtime.Instance, store wasmtime.Storelike) (int32, int32, error) {
|
||||
major := i.GetExport(store, "opa_wasm_abi_version").Global()
|
||||
minor := i.GetExport(store, "opa_wasm_abi_minor_version").Global()
|
||||
if major != nil && minor != nil {
|
||||
majorVal := major.Get(store)
|
||||
minorVal := minor.Get(store)
|
||||
if majorVal.Kind() == wasmtime.KindI32 && minorVal.Kind() == wasmtime.KindI32 {
|
||||
return majorVal.I32(), minorVal.I32(), nil
|
||||
}
|
||||
}
|
||||
return 0, 0, fmt.Errorf("failed to read ABI version")
|
||||
}
|
||||
|
||||
// Eval performs an evaluation of the specified entrypoint, with any provided
|
||||
// input, and returns the resulting value dumped to a string.
|
||||
func (i *VM) Eval(ctx context.Context, entrypoint int32, input *interface{}, metrics metrics.Metrics, seed io.Reader, ns time.Time) ([]byte, error) {
|
||||
if i.abiMinorVersion < int32(2) {
|
||||
return i.evalCompat(ctx, entrypoint, input, metrics, seed, ns)
|
||||
}
|
||||
|
||||
metrics.Timer("wasm_vm_eval").Start()
|
||||
defer metrics.Timer("wasm_vm_eval").Stop()
|
||||
|
||||
mem := i.memory.UnsafeData(i.store)
|
||||
inputAddr, inputLen := int32(0), int32(0)
|
||||
|
||||
// NOTE: we'll never free the memory used for the input string during
|
||||
// the one evaluation, but we'll overwrite it on the next evaluation.
|
||||
heapPtr := i.evalHeapPtr
|
||||
|
||||
if input != nil {
|
||||
metrics.Timer("wasm_vm_eval_prepare_input").Start()
|
||||
var raw []byte
|
||||
switch v := (*input).(type) {
|
||||
case []byte:
|
||||
raw = v
|
||||
case *ast.Term:
|
||||
raw = []byte(v.String())
|
||||
case ast.Value:
|
||||
raw = []byte(v.String())
|
||||
default:
|
||||
var err error
|
||||
raw, err = json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
inputLen = int32(len(raw))
|
||||
inputAddr = i.evalHeapPtr
|
||||
heapPtr += inputLen
|
||||
copy(mem[inputAddr:inputAddr+inputLen], raw)
|
||||
|
||||
metrics.Timer("wasm_vm_eval_prepare_input").Stop()
|
||||
}
|
||||
|
||||
// Setting the ctx here ensures that it'll be available to builtins that
|
||||
// make use of it (e.g. `http.send`); and it will spawn a go routine
|
||||
// cancelling the builtins that use topdown.Cancel, when the context is
|
||||
// cancelled.
|
||||
i.dispatcher.Reset(ctx, seed, ns)
|
||||
|
||||
metrics.Timer("wasm_vm_eval_call").Start()
|
||||
resultAddr, err := i.evalOneOff(ctx, int32(entrypoint), i.dataAddr, inputAddr, inputLen, heapPtr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metrics.Timer("wasm_vm_eval_call").Stop()
|
||||
|
||||
data := i.memory.UnsafeData(i.store)[resultAddr:]
|
||||
n := bytes.IndexByte(data, 0)
|
||||
if n < 0 {
|
||||
n = 0
|
||||
}
|
||||
|
||||
// Skip free'ing input and result JSON as the heap will be reset next round anyway.
|
||||
return data[:n], nil
|
||||
}
|
||||
|
||||
// evalCompat evaluates a policy using multiple calls into the VM to set the stage.
|
||||
// It's been superceded with ABI version 1.2, but still here for compatibility with
|
||||
// Wasm modules lacking the needed export (i.e., ABI 1.1).
|
||||
func (i *VM) evalCompat(ctx context.Context, entrypoint int32, input *interface{}, metrics metrics.Metrics, seed io.Reader, ns time.Time) ([]byte, error) {
|
||||
metrics.Timer("wasm_vm_eval").Start()
|
||||
defer metrics.Timer("wasm_vm_eval").Stop()
|
||||
|
||||
@@ -640,6 +733,12 @@ func callOrCancel(ctx context.Context, vm *VM, name string, args ...int32) (inte
|
||||
}
|
||||
}
|
||||
if msg != "" {
|
||||
// TODO(sr): Out of bounds memory access is a trap, too!
|
||||
// This "interrupted at" is a bit misleading, however, currently
|
||||
// the only way to fix this is by looking at the string
|
||||
// `t.Error()` which also contains a (long, prettily) rendered
|
||||
// backtrace.
|
||||
// See also https://github.com/bytecodealliance/wasmtime-go/issues/63
|
||||
msg = "interrupted at " + msg
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,7 @@
|
||||
|
||||
package capabilities
|
||||
|
||||
const abiVersion = 1
|
||||
const abiMinorVersion = 1
|
||||
|
||||
// ABIVersions returns the ABI versions that this SDK supports
|
||||
func ABIVersions() [][2]int {
|
||||
return [][2]int{{abiVersion, abiMinorVersion}}
|
||||
return [][2]int{{1, 1}, {1, 2}}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ func BenchmarkWasmRego(b *testing.B) {
|
||||
policy := compileRegoToWasm("a = true", "data.p.a = x", false)
|
||||
instance, _ := opa.New().
|
||||
WithPolicyBytes(policy).
|
||||
WithMemoryLimits(131070, 2*131070). // TODO: For some reason unlimited memory slows down the eval_ctx_new().
|
||||
WithPoolSize(1).
|
||||
Init()
|
||||
|
||||
|
||||
@@ -270,3 +270,23 @@ func TestRandSeedingOptions(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompatWithABIMinorVersion1(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
pq, err := New(
|
||||
LoadBundle("testdata/bundle.tar.gz"),
|
||||
Query("data.test.allow"),
|
||||
).PrepareForEval(ctx)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
|
||||
rs, err := pq.Eval(ctx, EvalInput(map[string]interface{}{"x": "x"}))
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
|
||||
assertResultSet(t, rs, `[[true]]`)
|
||||
}
|
||||
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
# NOTE(sr): this is the last image emitting wasm modules with ABI 1.1
|
||||
IMAGE := openpolicyagent/opa:0.30.1
|
||||
|
||||
bundle.tar.gz: t.rego
|
||||
docker run -v $$(pwd):/src -w /src $(IMAGE) build -t wasm -e test/allow t.rego
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
package test
|
||||
|
||||
default allow = false
|
||||
|
||||
allow {
|
||||
input.x == "x"
|
||||
}
|
||||
+29
-20
@@ -5,7 +5,6 @@ const { readFileSync, readdirSync } = require('fs');
|
||||
function stringDecoder(mem) {
|
||||
return function (addr) {
|
||||
const i8 = new Int8Array(mem.buffer);
|
||||
const start = addr;
|
||||
var s = "";
|
||||
while (i8[addr] != 0) {
|
||||
s += String.fromCharCode(i8[addr++]);
|
||||
@@ -93,16 +92,14 @@ function loadJSON(mod, memory, value) {
|
||||
}
|
||||
|
||||
function dumpJSON(mod, memory, addr) {
|
||||
|
||||
const rawAddr = mod.instance.exports.opa_json_dump(addr);
|
||||
return parseJSON(memory, rawAddr);
|
||||
}
|
||||
|
||||
function parseJSON(memory, rawAddr) {
|
||||
const buf = new Uint8Array(memory.buffer);
|
||||
|
||||
// NOTE(tsandall): There must be a better way of doing this...
|
||||
let idx = rawAddr;
|
||||
while (buf[idx] != 0) {
|
||||
idx++;
|
||||
}
|
||||
|
||||
const idx = rawAddr + buf.slice(rawAddr).findIndex((elem) => elem === 0);
|
||||
// TODO(sr): use TextDecoder and friends
|
||||
return JSON.parse(decodeURIComponent(escape(String.fromCharCode.apply(null, buf.slice(rawAddr, idx)))));
|
||||
}
|
||||
|
||||
@@ -202,19 +199,31 @@ async function instantiate(bytes, memory, data) {
|
||||
|
||||
function evaluate(policy, input) {
|
||||
|
||||
policy.module.instance.exports.opa_heap_ptr_set(policy.heapPtr);
|
||||
let inputLen = 0;
|
||||
let inputAddr = 0;
|
||||
if (input) {
|
||||
const inp = JSON.stringify(input);
|
||||
const buf = new Uint8Array(policy.memory.buffer);
|
||||
inputAddr = policy.heapPtr;
|
||||
inputLen = inp.length;
|
||||
|
||||
const inputAddr = loadJSON(policy.module, policy.memory, input);
|
||||
const ctxAddr = policy.module.instance.exports.opa_eval_ctx_new();
|
||||
for (let i = 0; i < inputLen; i++) {
|
||||
buf[inputAddr + i] = inp.charCodeAt(i);
|
||||
}
|
||||
policy.heapPtr = inputAddr + inputLen;
|
||||
}
|
||||
|
||||
policy.module.instance.exports.opa_eval_ctx_set_input(ctxAddr, inputAddr);
|
||||
policy.module.instance.exports.opa_eval_ctx_set_data(ctxAddr, policy.dataAddr);
|
||||
const addr = policy.module.instance.exports.opa_eval(
|
||||
0, // reserved
|
||||
0, // entrypoint
|
||||
policy.dataAddr,
|
||||
inputAddr,
|
||||
inputLen,
|
||||
policy.heapPtr,
|
||||
0, // json output
|
||||
);
|
||||
|
||||
policy.module.instance.exports.eval(ctxAddr);
|
||||
|
||||
const resultAddr = policy.module.instance.exports.opa_eval_ctx_get_result(ctxAddr);
|
||||
|
||||
return { addr: resultAddr };
|
||||
return { addr };
|
||||
}
|
||||
|
||||
function namespace(cache, key) {
|
||||
@@ -282,7 +291,7 @@ async function test() {
|
||||
const result = evaluate(policy, testCases[i].input);
|
||||
|
||||
const expDefined = testCases[i].want_defined;
|
||||
const rs = dumpJSON(policy.module, policy.memory, result.addr);
|
||||
const rs = parseJSON(policy.memory, result.addr);
|
||||
|
||||
if (expDefined !== undefined) {
|
||||
const len = rs.length
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "malloc.h"
|
||||
#include "context.h"
|
||||
#include "stdlib.h"
|
||||
#include "value.h"
|
||||
#include "json.h"
|
||||
|
||||
WASM_EXPORT(opa_eval_ctx_new)
|
||||
opa_eval_ctx_t *opa_eval_ctx_new()
|
||||
@@ -18,6 +21,38 @@ void opa_eval_ctx_set_input(opa_eval_ctx_t *ctx, opa_value *v)
|
||||
ctx->input = v;
|
||||
}
|
||||
|
||||
WASM_EXPORT(opa_eval)
|
||||
char *opa_eval(void *reserved, int entrypoint, opa_value *data, char *input, uint32_t input_len, uint32_t heap, bool want_value)
|
||||
{
|
||||
if (reserved != NULL) {
|
||||
opa_abort("invalid reserved argument");
|
||||
}
|
||||
|
||||
opa_heap_ptr_set(heap);
|
||||
opa_eval_ctx_t ctx = {
|
||||
.entrypoint = entrypoint,
|
||||
.data = data,
|
||||
.input = opa_value_parse(input, input_len),
|
||||
};
|
||||
|
||||
if (eval(&ctx) != 0) {
|
||||
opa_abort("eval failed");
|
||||
}
|
||||
if (want_value) {
|
||||
return opa_value_dump(ctx.result);
|
||||
}
|
||||
return opa_json_dump(ctx.result);
|
||||
}
|
||||
|
||||
// NOTE(sr): Without this attribute set, LLVM would not let this function
|
||||
// make it into the Wasm module unchanged. We need it there, so the wasm
|
||||
// compiler in OPA can replace _this_ eval with _its_ eval, compiled from
|
||||
// rego.
|
||||
__attribute__((optnone))
|
||||
int32_t eval(opa_eval_ctx_t *ctx) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
WASM_EXPORT(opa_eval_ctx_set_data)
|
||||
void opa_eval_ctx_set_data(opa_eval_ctx_t *ctx, opa_value *v)
|
||||
{
|
||||
|
||||
@@ -23,4 +23,6 @@ opa_value *opa_builtin2(int, void *, opa_value *, opa_value *);
|
||||
opa_value *opa_builtin3(int, void *, opa_value *, opa_value *, opa_value *);
|
||||
opa_value *opa_builtin4(int, void *, opa_value *, opa_value *, opa_value *, opa_value *);
|
||||
|
||||
int32_t eval(opa_eval_ctx_t *ctx);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user