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:
Stephan Renatus
2021-07-15 09:41:40 +02:00
committed by GitHub
parent f3284cfc7b
commit c0c3cd18a6
17 changed files with 257 additions and 72 deletions
+4
View File
@@ -3503,6 +3503,10 @@
{
"version": 1,
"minor_version": 1
},
{
"version": 1,
"minor_version": 2
}
]
}
+1 -1
View File
@@ -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
View File
@@ -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 |
|-------|------|-------------|
+6
View File
@@ -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
1 opa_agg_count opa_value_type
200 opa_cmp_lte opa_value_compare
201 opa_cmp_lte opa_boolean
202 opa_eval_ctx_new opa_malloc
203 opa_eval opa_abort
204 opa_eval opa_heap_ptr_set
205 opa_eval opa_value_parse
206 opa_eval eval
207 opa_eval opa_value_dump
208 opa_eval opa_json_dump
209 __force_import_opa_builtins opa_builtin0
210 __force_import_opa_builtins opa_builtin1
211 __force_import_opa_builtins opa_builtin2
File diff suppressed because one or more lines are too long
Binary file not shown.
+13 -20
View File
@@ -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{
+99
View File
@@ -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}}
}
-1
View File
@@ -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()
+20
View File
@@ -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]]`)
}
+5
View File
@@ -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
BIN
View File
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
package test
default allow = false
allow {
input.x == "x"
}
+29 -20
View File
@@ -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
+35
View File
@@ -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)
{
+2
View File
@@ -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