feat!: add Rego Virtual Machine (RVM) implementation (#495)

* feat!: add Rego Virtual Machine (RVM) implementation

This commit introduces a register-based virtual machine for executing Rego
policies with bytecode-style instructions. Unlike the existing tree-walking
interpreter, the RVM compiles policies into instruction sequences that operate
on virtual registers, offering better performance and optimization potential.

Core Components:

Instruction Set Architecture:
- Define instruction types for data operations, control flow, and builtins
- Implement instruction parameter encoding and display formatting
- Add instruction parser with comprehensive test coverage

Virtual Machine Engine:
- Register-based execution model with program counter management
- Loop execution supporting iterators, comprehensions, and quantifiers
- Function call handling with argument evaluation and context management
- Rule evaluation with default value resolution and virtual data support
- Arithmetic and comparison operation implementations

Program Representation:
- Program listing builder with instruction sequencing
- Rule tree construction for organizing policy rules
- Binary and JSON serialization for compiled programs
- Recompilation support for program modification

Testing Infrastructure:
- Extensive YAML test suites covering all VM features
- Rust unit tests for VM execution and instruction parsing
- Test suites for loops, comprehensions, builtins, and control flow

BREAKING CHANGE: Introduces new VM execution path alongside interpreter

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* docs: add detailed RVM architecture references

Introduce architecture.md explaining program artifacts, serialization, and runtime subsystems.
Document the full opcode catalog in instruction-set.md, including operands, parameter tables, and outcomes.
Walk through execution flow, stacks, and operational guidance in vm-runtime.md, tying the runtime to the new architecture docs.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2025-11-14 11:43:19 -06:00
committed by GitHub
parent 6dc505c88b
commit 49bd3c22f3
89 changed files with 19158 additions and 313 deletions

128
Cargo.lock generated
View File

@@ -18,9 +18,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "1.1.3"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
@@ -108,6 +108,16 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bincode"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
dependencies = [
"serde",
"unty",
]
[[package]]
name = "bit-set"
version = "0.8.0"
@@ -131,9 +141,9 @@ checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "borrow-or-share"
version = "0.2.2"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32"
checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c"
[[package]]
name = "bstr"
@@ -165,9 +175,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.2.43"
version = "1.2.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2"
checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -231,9 +241,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.50"
version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623"
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
dependencies = [
"clap_builder",
"clap_derive",
@@ -241,9 +251,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.50"
version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0"
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
dependencies = [
"anstream",
"anstyle",
@@ -543,9 +553,9 @@ dependencies = [
[[package]]
name = "icu_collections"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
dependencies = [
"displaydoc",
"potential_utf",
@@ -556,9 +566,9 @@ dependencies = [
[[package]]
name = "icu_locale_core"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
dependencies = [
"displaydoc",
"litemap",
@@ -569,11 +579,10 @@ dependencies = [
[[package]]
name = "icu_normalizer"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
@@ -584,42 +593,38 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
[[package]]
name = "icu_properties"
version = "2.0.1"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"potential_utf",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.0.1"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899"
[[package]]
name = "icu_provider"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
dependencies = [
"displaydoc",
"icu_locale_core",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
@@ -656,6 +661,8 @@ checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
dependencies = [
"equivalent",
"hashbrown 0.16.0",
"serde",
"serde_core",
]
[[package]]
@@ -738,9 +745,9 @@ checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "litemap"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]]
name = "lock_api"
@@ -982,9 +989,9 @@ dependencies = [
[[package]]
name = "potential_utf"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
dependencies = [
"zerovec",
]
@@ -1175,6 +1182,7 @@ name = "regorus"
version = "0.5.0"
dependencies = [
"anyhow",
"bincode",
"cfg-if",
"chrono",
"chrono-tz",
@@ -1183,6 +1191,7 @@ dependencies = [
"dashmap",
"data-encoding",
"globset",
"indexmap",
"ipnet",
"jsonschema",
"lazy_static",
@@ -1230,20 +1239,6 @@ name = "scientific"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38a4b339a8de779ecb098a772ecbba2ace74e23ed959a5b4f30631d8bf1799a8"
dependencies = [
"scientific-macro",
]
[[package]]
name = "scientific-macro"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2ee4885492bb655bfa05d039cd9163eb8fe9f79ddebf00ca23a1637510c2fd2"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.41",
"syn 2.0.108",
]
[[package]]
name = "scopeguard"
@@ -1416,9 +1411,9 @@ dependencies = [
[[package]]
name = "tinystr"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
dependencies = [
"displaydoc",
"zerovec",
@@ -1460,9 +1455,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "unicode-ident"
version = "1.0.20"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unicode-xid"
@@ -1476,6 +1471,12 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "unty"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
[[package]]
name = "url"
version = "2.5.7"
@@ -1777,9 +1778,9 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "writeable"
version = "0.6.1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "xtask"
@@ -1794,11 +1795,10 @@ dependencies = [
[[package]]
name = "yoke"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
@@ -1806,9 +1806,9 @@ dependencies = [
[[package]]
name = "yoke-derive"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.41",
@@ -1859,9 +1859,9 @@ dependencies = [
[[package]]
name = "zerotrie"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
dependencies = [
"displaydoc",
"yoke",
@@ -1870,9 +1870,9 @@ dependencies = [
[[package]]
name = "zerovec"
version = "0.11.4"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
dependencies = [
"yoke",
"zerofrom",
@@ -1881,9 +1881,9 @@ dependencies = [
[[package]]
name = "zerovec-derive"
version = "0.11.1"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.41",

View File

@@ -20,7 +20,7 @@ keywords = ["interpreter", "no_std", "opa", "policy-as-code", "rego"]
doctest = false
[features]
default = ["full-opa", "arc"]
default = ["full-opa", "arc", "rvm"]
arc = ["scientific/arc"]
ast = []
@@ -38,6 +38,7 @@ net = ["dep:ipnet"]
no_std = ["lazy_static/spin_no_std"]
opa-runtime = []
regex = ["dep:regex"]
rvm = ["dep:bincode", "dep:indexmap"]
semver = ["dep:semver"]
std = ["rand/std", "rand/std_rng", "serde_json/std", "msvc_spectre_libs" ]
time = ["dep:chrono", "dep:chrono-tz"]
@@ -99,7 +100,7 @@ lazy_static = { version = "1.4.0", default-features = false }
thiserror = { version = "2.0", default-features = false }
data-encoding = { version = "2.8.0", optional = true, default-features=false, features = ["alloc"] }
scientific = { version = "0.5.3" }
scientific = { version = "0.5.3", default-features = false }
globset = { version = "0.4.16", features = ["simd-accel"], default-features = false, optional = true }
regex = {version = "1.11.1", optional = true, default-features = false }
@@ -120,6 +121,10 @@ msvc_spectre_libs = { version = "0.1", features = ["error"], optional = true }
dashmap = { version = "6.1", default-features = false, optional = true }
mimalloc = { path = "mimalloc", optional = true }
# rvm related deps
indexmap = { version = "2.12.0", default-features = false, features = ["serde"], optional = true }
bincode = { version = "2.0.1", default-features = false, features = ["alloc", "serde"], optional = true }
[dev-dependencies]
anyhow = "1.0.45"
cfg-if = "1.0.0"

108
bindings/ffi/Cargo.lock generated
View File

@@ -18,9 +18,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "1.1.3"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
@@ -125,9 +125,9 @@ checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "borrow-or-share"
version = "0.2.2"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32"
checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c"
[[package]]
name = "bstr"
@@ -172,9 +172,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.43"
version = "1.2.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2"
checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -211,18 +211,18 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.50"
version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623"
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.50"
version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0"
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
dependencies = [
"anstream",
"anstyle",
@@ -431,9 +431,9 @@ dependencies = [
[[package]]
name = "icu_collections"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
dependencies = [
"displaydoc",
"potential_utf",
@@ -444,9 +444,9 @@ dependencies = [
[[package]]
name = "icu_locale_core"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
dependencies = [
"displaydoc",
"litemap",
@@ -457,11 +457,10 @@ dependencies = [
[[package]]
name = "icu_normalizer"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
@@ -472,42 +471,38 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
[[package]]
name = "icu_properties"
version = "2.0.1"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"potential_utf",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.0.1"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899"
[[package]]
name = "icu_provider"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
dependencies = [
"displaydoc",
"icu_locale_core",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
@@ -620,9 +615,9 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "litemap"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]]
name = "lock_api"
@@ -814,9 +809,9 @@ dependencies = [
[[package]]
name = "potential_utf"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
dependencies = [
"zerovec",
]
@@ -1022,20 +1017,6 @@ name = "scientific"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38a4b339a8de779ecb098a772ecbba2ace74e23ed959a5b4f30631d8bf1799a8"
dependencies = [
"scientific-macro",
]
[[package]]
name = "scientific-macro"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2ee4885492bb655bfa05d039cd9163eb8fe9f79ddebf00ca23a1637510c2fd2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "scopeguard"
@@ -1201,9 +1182,9 @@ dependencies = [
[[package]]
name = "tinystr"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
dependencies = [
"displaydoc",
"zerovec",
@@ -1252,9 +1233,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "unicode-ident"
version = "1.0.20"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unsafe-libyaml"
@@ -1534,17 +1515,16 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "writeable"
version = "0.6.1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "yoke"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
@@ -1552,9 +1532,9 @@ dependencies = [
[[package]]
name = "yoke-derive"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [
"proc-macro2",
"quote",
@@ -1605,9 +1585,9 @@ dependencies = [
[[package]]
name = "zerotrie"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
dependencies = [
"displaydoc",
"yoke",
@@ -1616,9 +1596,9 @@ dependencies = [
[[package]]
name = "zerovec"
version = "0.11.4"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
dependencies = [
"yoke",
"zerofrom",
@@ -1627,9 +1607,9 @@ dependencies = [
[[package]]
name = "zerovec-derive"
version = "0.11.1"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [
"proc-macro2",
"quote",

100
bindings/java/Cargo.lock generated
View File

@@ -18,9 +18,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "1.1.3"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
@@ -75,9 +75,9 @@ checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "borrow-or-share"
version = "0.2.2"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32"
checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c"
[[package]]
name = "bstr"
@@ -109,9 +109,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cc"
version = "1.2.43"
version = "1.2.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2"
checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -303,9 +303,9 @@ dependencies = [
[[package]]
name = "icu_collections"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
dependencies = [
"displaydoc",
"potential_utf",
@@ -316,9 +316,9 @@ dependencies = [
[[package]]
name = "icu_locale_core"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
dependencies = [
"displaydoc",
"litemap",
@@ -329,11 +329,10 @@ dependencies = [
[[package]]
name = "icu_normalizer"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
@@ -344,42 +343,38 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
[[package]]
name = "icu_properties"
version = "2.0.1"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"potential_utf",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.0.1"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899"
[[package]]
name = "icu_provider"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
dependencies = [
"displaydoc",
"icu_locale_core",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
@@ -502,9 +497,9 @@ checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "litemap"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]]
name = "lock_api"
@@ -690,9 +685,9 @@ dependencies = [
[[package]]
name = "potential_utf"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
dependencies = [
"zerovec",
]
@@ -893,20 +888,6 @@ name = "scientific"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38a4b339a8de779ecb098a772ecbba2ace74e23ed959a5b4f30631d8bf1799a8"
dependencies = [
"scientific-macro",
]
[[package]]
name = "scientific-macro"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2ee4885492bb655bfa05d039cd9163eb8fe9f79ddebf00ca23a1637510c2fd2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "scopeguard"
@@ -1064,9 +1045,9 @@ dependencies = [
[[package]]
name = "tinystr"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
dependencies = [
"displaydoc",
"zerovec",
@@ -1074,9 +1055,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.20"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unsafe-libyaml"
@@ -1352,17 +1333,16 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "writeable"
version = "0.6.1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "yoke"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
@@ -1370,9 +1350,9 @@ dependencies = [
[[package]]
name = "yoke-derive"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [
"proc-macro2",
"quote",
@@ -1423,9 +1403,9 @@ dependencies = [
[[package]]
name = "zerotrie"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
dependencies = [
"displaydoc",
"yoke",
@@ -1434,9 +1414,9 @@ dependencies = [
[[package]]
name = "zerovec"
version = "0.11.4"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
dependencies = [
"yoke",
"zerofrom",
@@ -1445,9 +1425,9 @@ dependencies = [
[[package]]
name = "zerovec-derive"
version = "0.11.1"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -18,9 +18,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "1.1.3"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
@@ -75,9 +75,9 @@ checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "borrow-or-share"
version = "0.2.2"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32"
checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c"
[[package]]
name = "bstr"
@@ -103,9 +103,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
[[package]]
name = "cc"
version = "1.2.43"
version = "1.2.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2"
checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -287,9 +287,9 @@ dependencies = [
[[package]]
name = "icu_collections"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
dependencies = [
"displaydoc",
"potential_utf",
@@ -300,9 +300,9 @@ dependencies = [
[[package]]
name = "icu_locale_core"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
dependencies = [
"displaydoc",
"litemap",
@@ -313,11 +313,10 @@ dependencies = [
[[package]]
name = "icu_normalizer"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
@@ -328,42 +327,38 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
[[package]]
name = "icu_properties"
version = "2.0.1"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"potential_utf",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.0.1"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899"
[[package]]
name = "icu_provider"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
dependencies = [
"displaydoc",
"icu_locale_core",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
@@ -473,9 +468,9 @@ checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "litemap"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]]
name = "lock_api"
@@ -685,9 +680,9 @@ checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "potential_utf"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
dependencies = [
"zerovec",
]
@@ -944,20 +939,6 @@ name = "scientific"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38a4b339a8de779ecb098a772ecbba2ace74e23ed959a5b4f30631d8bf1799a8"
dependencies = [
"scientific-macro",
]
[[package]]
name = "scientific-macro"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2ee4885492bb655bfa05d039cd9163eb8fe9f79ddebf00ca23a1637510c2fd2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "scopeguard"
@@ -1101,9 +1082,9 @@ dependencies = [
[[package]]
name = "tinystr"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
dependencies = [
"displaydoc",
"zerovec",
@@ -1111,9 +1092,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.20"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unindent"
@@ -1301,17 +1282,16 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "writeable"
version = "0.6.1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "yoke"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
@@ -1319,9 +1299,9 @@ dependencies = [
[[package]]
name = "yoke-derive"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [
"proc-macro2",
"quote",
@@ -1372,9 +1352,9 @@ dependencies = [
[[package]]
name = "zerotrie"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
dependencies = [
"displaydoc",
"yoke",
@@ -1383,9 +1363,9 @@ dependencies = [
[[package]]
name = "zerovec"
version = "0.11.4"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
dependencies = [
"yoke",
"zerofrom",
@@ -1394,9 +1374,9 @@ dependencies = [
[[package]]
name = "zerovec-derive"
version = "0.11.1"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [
"proc-macro2",
"quote",

100
bindings/wasm/Cargo.lock generated
View File

@@ -18,9 +18,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "1.1.3"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
@@ -75,9 +75,9 @@ checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "borrow-or-share"
version = "0.2.2"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32"
checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c"
[[package]]
name = "bstr"
@@ -103,9 +103,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
[[package]]
name = "cc"
version = "1.2.43"
version = "1.2.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2"
checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -296,9 +296,9 @@ dependencies = [
[[package]]
name = "icu_collections"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
dependencies = [
"displaydoc",
"potential_utf",
@@ -309,9 +309,9 @@ dependencies = [
[[package]]
name = "icu_locale_core"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
dependencies = [
"displaydoc",
"litemap",
@@ -322,11 +322,10 @@ dependencies = [
[[package]]
name = "icu_normalizer"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
@@ -337,42 +336,38 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
[[package]]
name = "icu_properties"
version = "2.0.1"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"potential_utf",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.0.1"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899"
[[package]]
name = "icu_provider"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
dependencies = [
"displaydoc",
"icu_locale_core",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
@@ -473,9 +468,9 @@ checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "litemap"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]]
name = "lock_api"
@@ -671,9 +666,9 @@ dependencies = [
[[package]]
name = "potential_utf"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
dependencies = [
"zerovec",
]
@@ -877,20 +872,6 @@ name = "scientific"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38a4b339a8de779ecb098a772ecbba2ace74e23ed959a5b4f30631d8bf1799a8"
dependencies = [
"scientific-macro",
]
[[package]]
name = "scientific-macro"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2ee4885492bb655bfa05d039cd9163eb8fe9f79ddebf00ca23a1637510c2fd2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "scopeguard"
@@ -1028,9 +1009,9 @@ dependencies = [
[[package]]
name = "tinystr"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
dependencies = [
"displaydoc",
"zerovec",
@@ -1038,9 +1019,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.20"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unsafe-libyaml"
@@ -1303,17 +1284,16 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "writeable"
version = "0.6.1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "yoke"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
@@ -1321,9 +1301,9 @@ dependencies = [
[[package]]
name = "yoke-derive"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [
"proc-macro2",
"quote",
@@ -1374,9 +1354,9 @@ dependencies = [
[[package]]
name = "zerotrie"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
dependencies = [
"displaydoc",
"yoke",
@@ -1385,9 +1365,9 @@ dependencies = [
[[package]]
name = "zerovec"
version = "0.11.4"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
dependencies = [
"yoke",
"zerofrom",
@@ -1396,9 +1376,9 @@ dependencies = [
[[package]]
name = "zerovec-derive"
version = "0.11.1"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [
"proc-macro2",
"quote",

274
docs/rvm/architecture.md Normal file
View File

@@ -0,0 +1,274 @@
# Regorus Virtual Machine Architecture
This document explains how Rego source becomes executable bytecode and how the
runtime evaluates it. It is meant for three audiences:
- **Engine developers** working on the RVM execution core and runtime subsystems.
- **Policy front-end authors** targeting the VM from alternate policy languages.
- **Operators/tools** wanting to reason about execution behaviour and
troubleshooting output.
The high-level pipeline looks like this:
```
┌───────────┐ emit Program ┌────────────┐ load & run ┌─────────┐
│ Parser & │ ───────────────▶ │ Program │ ─────────────▶ │ Rego VM │
│ Compiler │ (bytecode) │ Artifact │ (instructions│ Runtime │
└───────────┘ │ │ + metadata) │ │
└────────────┘ └─────────┘
```
Each step feeds the next via well-defined data structures described below.
---
## RVM in context
The Rego VM uses a register-based architecture with the following traits:
- **Register windows per frame**: Each rule or function call receives a
compile-time-sized register window. Windows are pooled and reused to keep the
runtime allocation profile predictable.
- **Sequential bytecode stream**: Fixed-width 32-bit instructions execute from
a linear program counter with optional jumps. Complex instructions reference
shared tables (`InstructionData`) that carry literals, loop metadata and call
parameters.
- **Literal and builtin tables**: Literal pools and builtin dispatch tables are
resolved at load time so bytecode stays compact and symbol lookups remain
constant-time during execution.
- **Extended control stacks**: Loop, rule-cache and comprehension stacks sit
alongside the core call stack, enabling suspension, short-circuiting and
deterministic rule caching without growing the register windows themselves.
---
## 1. Compilation Outputs
A successful compilation produces a `Program` (`src/rvm/program/core.rs`). The
layout is deliberately split:
- **Stable artifact section**: Always serialised and treated as canonical. It
captures the original policy sources, entry-points, compiler options,
etc.
- **Synthesised execution section**: It contains the
compiled instruction stream, instruction parameter tables, literal tables, etc.
It can be recreated from the stable artifact section if a future RVM version is
note able to deserialize it.
| Field | Purpose | Notes |
| :---------------------------------------------- | :--------------------------------------------------------------- | :---- |
| `instructions: Vec<Instruction>` | Ordered bytecode emitted by the compiler. | Each opcode is defined in `src/rvm/instructions/mod.rs` and executed by the dispatch tree. |
| `literals: Vec<Value>` | Literal constants shared across instructions. | Skipped by serde but written in the binary format via `BinaryValueSlice`; avoids duplicating large value graphs. |
| `instruction_data: InstructionData` | Parameter tables for complex opcodes. | Tables are indexed by `params_index` values stored in instructions. |
| `builtin_info_table: Vec<BuiltinInfo>` | Metadata for builtin calls. | Enforced and resolved by `Program::initialize_resolved_builtins`. |
| `entry_points: IndexMap<String, usize>` | Maps path names (e.g. `data.pkg.rule`) to starting PCs. | Preserves declaration order for tooling and serialized in the artifact section. |
| `sources: Vec<SourceFile>` | Captures original policy sources. | Stored in the stable artifact section alongside entry-points. |
| `rule_infos: Vec<RuleInfo>` | Metadata for every rule. | Includes register windows, default values, destructuring blocks. |
| `instruction_spans: Vec<Option<SpanInfo>>` | Optional span info for diagnostics. | Lines/columns mapped back into the source table when present. |
| `main_entry_point: usize` | Default bytecode entry point. | Used by loaders to jump into the top-level policy. |
| `max_rule_window_size` / `dispatch_window_size` | Register window sizing hints. | The VM uses these to size register banks up-front. |
| `metadata: ProgramMetadata` | Compilation metadata (`compiler_version`, etc.). | Helps operators verify provenance and tooling compatibility. |
| `rule_tree: Value` | Map of rule labels for conflict detection and lookups. | Serialized via `BinaryValueRef`; rebuilt into a `Value::Object` during load. |
| `resolved_builtins: Vec<BuiltinFcn>` | Resolved builtin function pointers. | Not serialized; repopulated by the host at load time. |
| `needs_runtime_recursion_check: bool` | Flags when `VirtualDataDocumentLookup` requires runtime guards. | Ensures the VM short-circuits recursion before hitting the instruction budget ceiling. |
| `needs_recompilation: bool` | Indicates partial deserialization of execution data. | Set when the extensible section fails; signals the loader to recompile. |
| `rego_v0: bool` | Records whether the policy targeted Rego v0 semantics. | Ensures recompilation preserves language-version behaviour. |
Additional helpers such as `Program::add_*`, `Program::update_*`, and
`Program::display_instruction_with_params` are used by the compiler and
inspection tooling to populate and render the program.
### Serialization layout
The module `src/rvm/program/serialization` writes `Program` instances into a
compact binary envelope that stays forward-compatible within a major format
version:
1. **Header**: magic `REGO` bytes followed by `SERIALIZATION_VERSION` (currently
`3`).
2. **Section manifest**: four little-endian `u32` lengths for entry points,
sources, literals, and the rule tree, plus a single-byte `rego_v0` flag.
3. **Preamble payloads**: each section is encoded with `bincode` using helper
wrappers (`BinaryValueSlice`, `BinaryValueRef`) to stream complex `Value`
graphs without cloning.
4. **Program core**: the remaining `Program` struct is serialized once more via
`bincode`; fields skipped by serde (entry points, literals, sources,
rule_tree, resolved builtins) are re-inserted from the preamble when the
program is reconstructed.
During deserialization the loader sanity-checks the header, lengths, and
version before decoding each preamble section. Any failure while decoding the
core payload downgrades the result to `DeserializationResult::Partial`,
preserving enough artifact data to trigger a recompilation. Successful loads
call `Program::initialize_resolved_builtins` so host runtimes can plug in their
builtin implementations.
---
## 2. Runtime Subsystems
At evaluation time the `RegoVM` (`src/rvm/vm/machine.rs`) consumes a `Program`
and exposes execution APIs. The VM separates concerns through specialised
stacks and caches.
````text
Runtime stacks (run-to-completion)
┌──────────────────────────── RegoVM ─────────────────────────────┐
│ Registers (active window) ─────┐ │
│ Program counter (pc) ───────┐ │ │
│ ▼ ▼ │
│ Control flow dispatcher ───────────────▶ Instruction stream │
│ ▲ ▲ │
│ Rule cache ────────┐ │ │ Loop stack (LoopContext) │
│ Evaluation cache │ │ └──▶ Comprehension stack │
│ Host await queue ──┴─▶ Return values / suspensions │
└─────────────────────────────────────────────────────────────────┘
Suspendable mode frame stack
┌───────────────────────────────────────────────────────────────────────┐
│ Frame stack │
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌──────────────────────┐ │
│ │ RuleFrame │ → │ LoopFrame │ → │ ComprehensionFrame │ │
│ └────────────────┘ └────────────────┘ └──────────────────────┘ │
│ ▲ ▲ ▲ │
│ │ push frame │ push frame │ push frame│
│ ▼ ▼ ▼ │
│ allow { ... } │
│ some user in input.users │
│ [x | ... ] │
└─────────┴─────────────────────────────────────────────────────────────┘
Execution state machine (suspendable)
┌──────────────────────┐
│ Suspended │
└─────▲────────────┬───┘
│ │
| │
│ │
│ │
HostAwait/Breakpoint/Step │ | resume
│ │
│ │
| ▼
┌──────────┐ ┌───────────────────────────┐ Return ┌────────────┐
│ Ready ├─────────────▶│ Running │────────────▶│ Completed │
└──────────┘ └────────────┬──────────────┘ └────────────┘
│ VmError
┌──────────┐
│ Error │
└──────────┘
````
Key state:
- **Registers**: The active register window for the current frame. Windows are
allocated per rule call using a register pool to minimise allocations.
- **Program counter (`pc`)**: The bytecode index for run-to-completion mode. In
suspendable mode, each frame tracks its own `pc`.
- **Rule cache**: Stores results and completion flags per rule to avoid
recomputation.
- **Loop/comprehension stacks**: Track iteration state, completion criteria, and
pending yields.
- **Execution stack**: Present in suspendable mode. Stores `ExecutionFrame`
objects (`FrameKind::Rule`, `Loop`, `Comprehension`) so that the VM can pause
and resume evaluation cleanly.
- **Host await responses**: For run-to-completion execution, pre-defined values
keyed by identifier. Suspendable mode instead returns a
`SuspendReason::HostAwait` to the caller.
- **Evaluation cache**: Used by `VirtualDataDocumentLookup` to memoise path
results.
---
## 3. Execution Modes
The VM supports two execution styles selected via `set_execution_mode`.
### Run-to-completion
- Entry point: `RegoVM::execute` or `execute_entry_point_by_{index,name}`.
- Control loop: `execute_run_to_completion` → `jump_to` which iterates the
instruction stream sequentially.
- Suspension: Unsupported. Any instruction that would suspend emits a runtime
error because the host cannot resume.
- Traps: Instruction budget enforced via `max_instructions`; exceeding the limit
returns `VmError::InstructionLimitExceeded`.
### Suspendable
- Entry point: same as above, but the VM calls `run_stackless_from` which pushes
a main `ExecutionFrame` and dispatches instructions through
`run_stackless_loop`.
- Frames: Each instruction can adjust the currently active frame or push/pop
new frames (rule calls, loops, comprehensions).
- Suspension: `InstructionOutcome::Suspend` transitions the VM into
`ExecutionState::Suspended` with a `SuspendReason` (host await, breakpoint,
single-step). The host must call `resume` with an optional value to continue.
- Breakpoints & step mode: Configured via `set_step_mode` and breakpoint
mutators on `ExecutionState`. Execution halts when a frame `pc` matches a
registered breakpoint.
In both modes the VM constantly validates safety conditions: parameter indices
must resolve, register windows must exist, and results must stay inside the
supported `Value` lattice. Errors are reported as `VmError` variants that
include formatted state snapshots where possible.
---
## 4. Data-flow Walkthrough
1. **Rule entry**: The compiler emits a `CallRule` instruction referencing a rule
index. The VM first consults `rule_cache[rule_index]`; non-function rules that
have already executed within the current top-level run reuse the cached
result. When the cache is cold, the VM pushes a new rule frame, allocates a
register window and jumps to the rule entry point. Function rules always run
afresh today—per-specialisation memoization is not yet implemented.
2. **Literal loads**: `Load` and `Load*` instructions fill registers from the
literal table or other sources (`LoadData`, `LoadInput`).
3. **Loops**: `LoopStart` fetches `LoopStartParams` from `InstructionData`,
initialises a `LoopContext`, and either pushes a new execution frame (for
suspendable mode) or updates `loop_stack`. `LoopNext` consults loop mode
(`Any`, `Every`, `ForEach`) to decide whether to continue or short-circuit.
4. **Comprehensions**: `ComprehensionBegin`/`Yield`/`End` manage collection
builders stored in a `ComprehensionContext`. Nested comprehensions stack
cleanly with loops.
5. **Assertions**: `AssertCondition` and `AssertNotUndefined` enforce Rego's
truthiness semantics. Inside loops/comprehensions they flag the current
iteration as failed (or short-circuit `every` loops to `false`); outside loop
contexts they raise `VmError::AssertionFailed`, mirroring Rego's runtime
errors for failed guards.
6. **Builtins & functions**: `BuiltinCall` reads `BuiltinCallParams`, resolves
the host function via `get_resolved_builtin`, and writes the result. Function
rules use `FunctionCallParams` to marshal arguments and run in the same
pipeline; repeat invocations with the same arguments are recomputed until the
VM grows specialisation-aware caching.
7. **Host await**: In run-to-completion mode, `HostAwait` consumes a response
from `host_await_responses`. Suspendable mode yields control with a
`SuspendReason::HostAwait { dest, argument, identifier }` that the host must
service.
8. **Completion**: `Return` wraps the selected register value into
`InstructionOutcome::Return`, unwinding frames until the entry frame is
cleared. `RuleReturn` is a specialised variant used by rule execution
helpers.
Throughout execution, diagnostics (register snapshots, loop counters, cache
hits) can be collected via `RegoVM` accessors. Integration tests in
`tests/rvm/vm/suites` exercise the most complex combinations of loops,
comprehensions and host calls; `complex.yaml` is a good starting point for
understanding real-world instruction streams.
---
## 5. Related Documentation
- [Instruction Set Reference](instruction-set.md)
- [VM Runtime Walkthrough](vm-runtime.md)

238
docs/rvm/instruction-set.md Normal file
View File

@@ -0,0 +1,238 @@
# RVM Instruction Set Reference
This reference captures every opcode emitted by the compiler and executed by
`RegoVM`. Each instruction is defined in `src/rvm/instructions/mod.rs` and
implemented by the dispatcher tree in `src/rvm/vm/dispatch.rs` plus specialised
submodules (`arithmetic.rs`, `loops.rs`, `functions.rs`, `rules.rs`,
`comprehension.rs`, `virtual_data.rs`).
Use this guide to understand operand semantics, parameter tables, and runtime
side effects.
---
## Reading the tables
- **Operands**: registers (`rX`), literals (`litY`), parameter indices (`pZ`) and
immediate values.
- **Parameters**: links into `InstructionData` (`src/rvm/instructions/params.rs`).
The compiler stores complex metadata here; instructions reference it by index.
- **Outcome**: mentioned in prose where relevant (`Continue`, `Return`, `Break`,
`Suspend`).
---
## Load and Move instructions
| Mnemonic | Operands | Behaviour |
| :--------- | :-------------------------- | :--------------------------------------------------- |
| `Load` | `dest=rD, literal_idx=litN` | Copies literal `N` into register `D`. |
| `LoadTrue` | `dest=rD` | Stores boolean `true`. |
| `LoadFalse`| `dest=rD` | Stores boolean `false`. |
| `LoadNull` | `dest=rD` | Stores `Value::Null`. |
| `LoadBool` | `dest=rD, value` | Stores inline boolean literal. |
| `LoadData` | `dest=rD` | Stores the VM's `data` value. |
| `LoadInput`| `dest=rD` | Stores the VM's `input` value. |
| `Move` | `dest=rD, src=rS` | Copies register `S` into register `D`. |
Out-of-range literal indices raise `VmError::LiteralIndexOutOfBounds`. Registers
must have been allocated by the current frame.
---
## Arithmetic and comparison instructions
| Mnemonic | Operands | Behaviour |
| :------- | :---------------------- | :------------------------------------------------------------ |
| `Add` | `dest, left, right` | Numeric addition; undefined operands trigger loop condition checks. |
| `Sub` | `dest, left, right` | Numeric subtraction. |
| `Mul` | `dest, left, right` | Numeric multiplication. |
| `Div` | `dest, left, right` | Numeric division with runtime checks (division by zero errors). |
| `Mod` | `dest, left, right` | Modulo. |
| `Eq` | `dest, left, right` | Equality comparison resulting in `Value::Bool`. |
| `Ne` | `dest, left, right` | Inequality. |
| `Lt`/`Le`/`Gt`/`Ge` | `dest, left, right` | Ordering comparisons. |
| `And` | `dest, left, right` | Logical conjunction (truthiness semantics). |
| `Or` | `dest, left, right` | Logical disjunction. |
| `Not` | `dest, operand` | Logical negation. |
| `AssertCondition` | `condition` | Fails current loop/rule when the condition is falsey. |
| `AssertNotUndefined` | `register` | Fails when register holds `Value::Undefined`. |
`handle_condition` routes through `loops.rs` to propagate failures to loop and
comprehension contexts. Outside loops it aborts the current rule.
---
## Collection and indexing instructions
| Mnemonic | Operands / Params | Behaviour |
| :------------------------- | :---------------------------- | :---------------------------------------------------------- |
| `ObjectSet` | `obj, key, value` | Mutates object in `obj` with key/value from registers. |
| `ObjectCreate` | `params_index=pN` | Builds object from literal template and register entries. |
| `ArrayNew` | `dest` | Creates empty array. |
| `ArrayPush` | `arr, value` | Appends to array. |
| `ArrayCreate` | `params_index=pN` | Builds array from register list; undefined element ⇒ result undefined. |
| `SetNew` | `dest` | Creates empty set. |
| `SetAdd` | `set, value` | Adds element to set. |
| `SetCreate` | `params_index=pN` | Builds set from register list; undefined element ⇒ result undefined. |
| `Index` | `dest, container, key` | Indexes container with runtime key. |
| `IndexLiteral` | `dest, container, literal_idx`| Indexes container using literal stored in program. |
| `ChainedIndex` | `params_index=pN` | Resolves multi-hop path from root register. |
| `Contains` | `dest, collection, value` | Checks membership; returns `Value::Bool`. |
| `Count` | `dest, collection` | Returns length or `Value::Undefined` for unsupported types. |
| `VirtualDataDocumentLookup`| `params_index=pN` | Evaluates `data` path, invoking rules lazily. |
Parameter structures:
- `ObjectCreateParams` reuses arrays of literal key/value pairs and register
pairs. Literal keys must be sorted to match template order.
- `ArrayCreateParams` and `SetCreateParams` store register lists. The VM checks
all referenced registers for `Value::Undefined` before constructing the
collection.
- `VirtualDataDocumentLookupParams` and `ChainedIndexParams` encode `Vec<LiteralOrRegister>`
path components. `LiteralOrRegister` is defined in `src/rvm/instructions/types.rs`.
---
## Loop instructions
Loops use dedicated parameter tables (`LoopStartParams`) and the `LoopMode`
enum.
| Mnemonic | Operands / Params | Behaviour |
| :---------- | :----------------------- | :------------------------------------------------------------- |
| `LoopStart` | `params_index=pN` | Initialises loop context and decides first body iteration. |
| `LoopNext` | `body_start`, `loop_end` | Finalises iteration, updates accumulators, advances to next element. |
`LoopMode` values:
- `Any`: succeed on first passing iteration, short-circuit on success.
- `Every`: fail on first failing iteration.
- `ForEach`: evaluate all iterations, typically for comprehensions or complete
rules.
`LoopStartParams` fields:
- `collection`: source register.
- `key_reg` / `value_reg`: iteration registers (for arrays, key is index).
- `result_reg`: accumulator storing loop outcome (`bool` for quantifiers).
- `body_start` / `loop_end`: PCs identifying loop boundaries.
The dispatcher converts `LoopStartParams` into a VM-specific `LoopParams` used by
both execution modes. In suspendable mode, loops own their own `ExecutionFrame`.
---
## Comprehension instructions
| Mnemonic | Operands / Params | Behaviour |
| :------------------- | :---------------------- | :------------------------------------------------- |
| `ComprehensionBegin` | `params_index=pN` | Allocates collection builder and iteration context. |
| `ComprehensionYield` | `value_reg`, `key_reg?` | Emits value (and optional key) into builder. |
| `ComprehensionEnd` | — | Finalises collection and stores result. |
`ComprehensionBeginParams` captures:
- `mode: ComprehensionMode` (Set, Array, Object)
- `collection_reg`: source register for iteration
- `result_reg`: register that will hold the final collection
- `key_reg` / `value_reg`: iteration registers
- `body_start` / `comprehension_end`: branch targets
Comprehensions manage their own stack (`ComprehensionContext`) to maintain
ordering guarantees (arrays), uniqueness (sets) or key/value pairing (objects).
---
## Call and return instructions
| Mnemonic | Operands / Params | Behaviour |
| :-------------------- | :------------------------ | :---------------------------------------------- |
| `BuiltinCall` | `params_index=pN` | Invokes builtin via resolved function pointer. |
| `FunctionCall` | `params_index=pN` | Invokes function rule. |
| `CallRule` | `dest, rule_index` | Requests rule evaluation with caching. |
| `RuleInit` | `result_reg, rule_index` | Prepares rule accumulator and cache state. |
| `Return` | `value_reg` | Returns value from current function body. |
| `RuleReturn` | — | Finalises rule evaluation frame. |
| `DestructuringSuccess`| — | Signals successful destructuring, breaks rule block. |
Parameter tables:
- `BuiltinCallParams` / `FunctionCallParams` store destination register, index
into builtin table / rule index, argument count and up to eight argument
register numbers.
- The VM dynamically resizes registers when a callee requires a larger window
using program metadata (`max_rule_window_size`).
---
## Host interaction
| Mnemonic | Operands | Behaviour |
| :--------- | :---------------- | :--------------------------------------- |
| `HostAwait`| `dest, arg, id` | Yields control to host with payload value. |
- Run-to-completion: consumes a response from `host_await_responses` keyed by
the identifier register. Missing responses raise `VmError::HostAwaitResponseMissing`.
- Suspendable: emits `InstructionOutcome::Suspend` with `SuspendReason::HostAwait`.
The host must resume with a value that will be written into `dest`.
---
## Halt instruction
| Mnemonic | Behaviour | Notes |
| :------- | :-------------------------------- | :---- |
| `Halt` | Terminates execution immediately. | Used during debugging or emitted for guard rails. |
When encountered during run-to-completion execution, `Halt` returns the current
value in register `0`.
---
## Parameter data overview
`InstructionData` (`src/rvm/instructions/params.rs`) collects all complex
parameter types. Each `add_*` method returns a `u16` index suitable for storing
inside instructions. The VM retrieves tables via `get_*` accessors.
| Struct | Field | Purpose |
| :----------------------- | :---------------------------------------- | :------------------------------------------------------------------------- |
| `LoopStartParams` | `mode` | Loop semantics (`Any`, `Every`, `ForEach`). |
| | `collection` | Register holding the iterable collection. |
| | `key_reg` / `value_reg` | Registers populated with the current key/value each iteration. |
| | `result_reg` | Accumulator for loop outcome (`bool` for quantifiers). |
| | `body_start` / `loop_end` | Instruction pointers delimiting the loop body and exit. |
| `BuiltinCallParams` | `dest` | Register that receives the builtin result. |
| | `builtin_index` | Slot into `builtin_info_table` for dispatch. |
| | `num_args` | Count of argument registers actually populated. |
| | `args[8]` | Up to eight registers supplying builtin arguments. |
| `FunctionCallParams` | `dest` | Register that receives the function rule result. |
| | `func_rule_index` | Rule index for the target function definition. |
| | `num_args` | Number of argument registers provided. |
| | `args[8]` | Argument register numbers (unused slots ignored). |
| `ObjectCreateParams` | `dest` | Destination register for the constructed object. |
| | `template_literal_idx` | Literal template containing all expected keys. |
| | `literal_key_fields: Vec<(u16, u8)>` | Mapping of literal-key indices to value registers. |
| | `fields: Vec<(u8, u8)>` | Dynamic key/value register pairs for non-literal keys. |
| `ArrayCreateParams` | `dest` | Destination register for the array literal. |
| | `elements: Vec<u8>` | Registers providing array elements (order preserved). |
| `SetCreateParams` | `dest` | Destination register for the set literal. |
| | `elements: Vec<u8>` | Registers providing set members (duplicates dropped at runtime). |
| `VirtualDataDocumentLookupParams` | `dest` | Destination register for lookup result. |
| | `path_components: Vec<LiteralOrRegister>` | Ordered path traversal steps; mix of literals and register-based keys. |
| `ChainedIndexParams` | `dest` | Destination register for resolved value. |
| | `root` | Register containing the root object/collection. |
| | `path_components: Vec<LiteralOrRegister>` | Path components applied relative to the root register. |
| `ComprehensionBeginParams` | `mode` | Comprehension output type (array, set, object). |
| | `collection_reg` | Source collection register for iteration. |
| | `result_reg` | Register receiving the final collection. |
| | `key_reg` / `value_reg` | Iteration registers (keys optional for arrays/sets). |
| | `body_start` / `comprehension_end` | Instruction pointers framing comprehension body and exit. |
All parameter structs derive `Serialize`/`Deserialize` and can be stored inside
artifacts. Some contain `Vec` fields; the compiler is responsible for ensuring
indices remain valid and stable across serialization boundaries.
---

243
docs/rvm/vm-runtime.md Normal file
View File

@@ -0,0 +1,243 @@
# VM Runtime Walkthrough
This document explains the runtime architecture implemented under
`src/rvm/vm`. It focuses on the `RegoVM` struct, execution modes, and the
responsibilities of each support module.
---
## 1. RegoVM structure
`src/rvm/vm/machine.rs` defines the public entry point. The table below maps its
fields to responsibilities.
| Field | Purpose | Related modules |
| :------------------------------------ | :---------------------------------------------------------- | :-------------- |
| `registers: Vec<Value>` | Active register window for the current frame. | `execution.rs`, `dispatch.rs` |
| `pc: usize` | Instruction pointer in run-to-completion mode. | `execution.rs` |
| `program: Arc<Program>` | Loaded program artifact. | `program/core.rs` |
| `compiled_policy` | Optional legacy default-rule support. | `crate::CompiledPolicy` |
| `rule_cache: Vec<(bool, Value)>` | Memoized rule results (bool = computed). | `rules.rs` |
| `data`, `input` | Global documents injected by host. | `dispatch.rs`, `virtual_data.rs` |
| `loop_stack` | Stack of `LoopContext` for run-to-completion loops. | `loops.rs` |
| `call_rule_stack` | Stack of `CallRuleContext` for nested rule calls. | `rules.rs` |
| `register_stack` | Saves prior register windows during run-to-completion rule calls. | `rules.rs`, `state.rs` |
| `comprehension_stack` | Active `ComprehensionContext` objects. | `comprehension.rs` |
| `base_register_count` | Root window size derived from program metadata. | `load_program` |
| `register_window_pool` | Recycled register vectors to reduce allocations. | `state.rs`, `rules.rs` |
| `max_instructions`, `executed_instructions` | Instruction budget and counter. | `execution.rs` |
| `evaluated` | Cache for virtual document lookups. | `virtual_data.rs` |
| `cache_hits` | Counters aiding diagnostics. | `virtual_data.rs` |
| `execution_stack` | Explicit frame stack for suspendable mode. | `execution_model.rs` |
| `execution_state` | `ExecutionState` enum capturing Ready/Running/Suspended/Error/Completed. | `execution_model.rs`, `execution.rs` |
| `breakpoints` | Set of PCs that trigger suspension. | `execution_model.rs` |
| `step_mode` | Enables single-step suspension after each instruction. | `execution.rs` |
| `host_await_responses` | Pre-scripted responses keyed by identifier (run-to-completion). | `dispatch.rs` |
| `execution_mode` | `RunToCompletion` or `Suspendable`. | `execution.rs` |
| `frame_pc_overridden` | Tracks manual PC updates inside frames. | `execution.rs`, `loops.rs`, `comprehension.rs` |
| `strict_builtin_errors` | Configures builtin failure handling (error vs `undefined`). | `machine.rs`, `arithmetic.rs`, `dispatch.rs` |
### Key methods
- `new` / `new_with_policy`: initialise VM with default register windows and
instruction limits.
- `load_program`: attaches a compiled `Program`, resizes registers, seeds rule
cache and resets counters.
- `set_data` / `set_input`: inject host documents. `set_data` runs
`Program::check_rule_data_conflicts` to guard against rule/data collisions.
- `set_max_instructions`, `set_execution_mode`, `set_step_mode`: configure
runtime policy.
- `set_host_await_responses`: used in run-to-completion mode when host await
responses are known ahead of time.
- `set_strict_builtin_errors`: toggles builtin failure semantics between
`VmError::ArithmeticError` and returning `Value::Undefined`.
- Accessors (`get_pc`, `get_registers`, `get_loop_stack`, etc.) aid debugging
and visualisation tooling.
---
## 2. Execution modes
### Run-to-completion
- Entry path: `execute()` or `execute_entry_point_by_*` when
`ExecutionMode::RunToCompletion`.
- `execute_run_to_completion` resets state, marks `ExecutionState::Running` and
calls `jump_to(start_pc)`.
- `jump_to` loops over instructions, updating `pc` and calling
`execute_instruction`. The loop stops on `Return`, `Break`, or `VmError`.
`Break` (emitted by `RuleReturn` and `DestructuringSuccess`) returns
register 0 to the caller for compatibility with rule evaluation.
- Suspension is not allowed; encountering an instruction that would suspend
(e.g. `HostAwait`) raises an internal error.
- Instruction budgets trigger `VmError::InstructionLimitExceeded` and switch the
state to `ExecutionState::Error`.
### Suspendable
- Entry path: same public API, but the VM calls `run_stackless_from`.
- `run_stackless_from` pushes an initial `ExecutionFrame::main(start_pc, 0)`
onto `execution_stack` and dispatches instructions via `run_stackless_loop`.
- Each frame tracks its own `pc` and `FrameKind` (`Main`, `Rule`, `Loop`,
`Comprehension`).
- `RuleFrameData` carries scheduling cursors, register window sizing, and saved
copies of the caller's registers and stacks so finalisation can restore the
original context.
- Suspension: `InstructionOutcome::Suspend` records a `SuspendReason` (host
await, breakpoint, step) and stores the last result snapshot. Host code calls
`resume(resume_value)` to continue.
- Completion: when `execution_stack` becomes empty the VM sets
`ExecutionState::Completed { result }`.
`execution_model.rs` defines the frame types and state machine:
- `ExecutionFrame`: captures the frame-local `pc` together with its
`FrameKind` payload.
- `RuleFrameData`: tracks rule index, scheduling phase, register window sizing,
and the saved caller state (`saved_registers`, `saved_loop_stack`,
`saved_comprehension_stack`).
- `SuspendReason`: currently surfaced values are host await, breakpoint, and
step; additional variants (`SuspendInstruction`, `InstructionLimit`,
`External`) are reserved for future instructions.
---
## 3. Instruction dispatch
`dispatch.rs` routes each `Instruction` variant through layered helpers
(`execute_load_and_move`, `execute_arithmetic_instruction`, `execute_call_instruction`, etc.). Control
flow hinges on the `InstructionOutcome` enum:
- `Continue`: normal execution; the caller increments the frame `pc`.
- `Return(Value)`: unwinds the current rule/function frame, propagating the
value upward.
- `Break`: used for rule-specific constructs (destructuring success, rule
return) to exit to the owning frame without returning a value.
- `Suspend { reason }`: used exclusively in suspendable mode.
Arithmetic and comparison opcodes live in `arithmetic.rs`, honouring
`strict_builtin_errors` when operand types differ. Collection, loop, and
virtual-data operations share helpers that convert `Value` variants with runtime
type checking. Errors become `VmError` variants to ensure consistent reporting.
`Halt` returns the value stored in register 0, allowing bytecode to terminate
early without suspending.
---
## 4. Loops and comprehensions
`loops.rs` implements iteration. Major components:
- `LoopContext`: stores iteration state (`IterationState` enum), key/value/result
registers, body and exit PCs, counters, and loop mode.
- `IterationState`: variants for arrays, objects, sets. Tracks progress for both
execution modes.
- `LoopMode`: `Any`, `Every`, `ForEach` controls short-circuit behaviour.
`execute_loop_start` initialises iteration, pushing the context onto
`loop_stack` (run-to-completion) or embedding it into a `FrameKind::Loop`
(suspendable). `LoopParams` carries the bytecode offsets, registers, and
destinations required by the instruction. `LoopNext` evaluates the previous
iteration outcome, updates `success_count`, advances the iterator, overrides
the caller's PC when needed, and decides whether to continue or exit.
`comprehension.rs` parallels `loops.rs` but maintains builder collections in
`ComprehensionContext`. The context stores:
- Builder value (array, set, object).
- Pending key/value registers.
- `body_start` / `comprehension_end` PCs.
- `iteration_state` for nested loops bound to the comprehension.
`ComprehensionYield` writes to the builder, respecting set uniqueness and object
key/value pairing. `ComprehensionEnd` publishes the result to `result_reg` and
pops the context.
---
## 5. Rule execution and caching
`rules.rs` and `functions.rs` coordinate rule calls:
- `execute_call_rule` dispatches based on execution mode. Both paths consult
`rule_cache` and short-circuit if the result is already available.
- Run-to-completion (`execute_call_rule_common`): swaps the active register,
loop, and comprehension stacks; pushes them onto `register_stack`; and drives
bodies via `jump_to`. Successful results are cached for non-function rules.
- Suspendable (`execute_call_rule_suspendable`): builds a `RuleFrameData`
containing saved registers/stacks and pushes a `FrameKind::Rule` so the
stackless loop can schedule destructuring, bodies, and finalisation.
- `execute_rule_init` writes the rule result register to `Value::Undefined`
(or initialises sets/objects) before running the bodies and records the
result register in the active `CallRuleContext`.
- `execute_rule_return` lets the scheduler finalise the frame and propagate the
cached value to the caller.
- `functions.rs::execute_function_call` prepares argument registers and delegates
to `execute_call_rule*`, enforcing arity via `BuiltinInfo` metadata.
Rule destructuring relies on `CallRuleContext`,
`RuleFramePhase::ExecutingDestructuring`, and the `DestructuringSuccess`
instruction to detect when pattern matching succeeded before entering the body.
---
## 6. Virtual data lookups
`virtual_data.rs` implements `VirtualDataDocumentLookup` and caches intermediate
path results in the VM's `evaluated` field (using `Value::Undefined` as a
sentinel) to avoid repeated rule evaluations. The compiler may set
`Program::needs_runtime_recursion_check`; the runtime currently relies on the
instruction budget and caching to prevent runaway recursion. Paths consist of
literals and register values supplied via `VirtualDataDocumentLookupParams`.
`ChainedIndex` follows a similar pattern but operates on register roots instead
of the global `data` namespace.
---
## 7. Error handling and diagnostics
`errors.rs` defines the `VmError` enum. Common variants include:
- `InstructionLimitExceeded`
- `LiteralIndexOutOfBounds`
- `RegisterNotArray` / `RegisterNotObject`
- `InvalidEntryPointIndex` / `EntryPointNotFound`
- `ArithmeticError`
- `RuleDataConflict`
- `HostAwaitResponseMissing`
- `Internal(String)` for invariant violations
`execution.rs::handle_instruction_error` centralises error propagation. In
suspendable mode it unwinds frames while preserving partial results where
possible. Run-to-completion mode returns the error immediately.
`state.rs` provides helpers to reset the VM and emit debug snapshots used in
assertion messages. When an internal invariant fails, the error message includes
`self.get_debug_state()` to help diagnose the issue.
---
## 8. Operational guidance
- **Instruction budgets**: adjust via `set_max_instructions` when running
untrusted policies. Inspect `executed_instructions` after completion.
- **Breakpoints & stepping**: populate `breakpoints` with bytecode PCs (see the
assembly listing) and enable `set_step_mode(true)` to pause after each
instruction.
- **Host await**: in run-to-completion mode, configure `set_host_await_responses`
before execution. In suspendable mode, expect `ExecutionState::Suspended {
reason: HostAwait { .. } }` and resume with the chosen value.
- **Builtin strictness**: `set_strict_builtin_errors(true)` reports type
mismatches as `VmError::ArithmeticError`; leave it `false` to coerce results
to `Value::Undefined`.
- **State inspection**: use getters (`get_registers`, `get_call_stack`,
`get_loop_stack`, `get_cache_hits`) to instrument evaluation or build
debugging UIs. `get_debug_state()` provides a concise snapshot for logs.
- **Testing**: YAML suites under `tests/rvm/vm/suites` exercise loops,
comprehensions, virtual data, host awaits, and serialization. `complex.yaml`
combines nested loops, comprehensions, function calls, and host awaits.
---

View File

@@ -16,7 +16,7 @@ if [ -f Cargo.toml ]; then
# Build for a target that has no std available.
if command -v rustup > /dev/null; then
rustup target add thumbv7m-none-eabi
(cd tests/ensure_no_std; cargo build -r --target thumbv7m-none-eabi)
(cd tests/ensure_no_std; cargo build -r --target thumbv7m-none-eabi --no-default-features --features opa-no-std)
fi
# Ensure that we can build with only std.

View File

@@ -38,6 +38,8 @@ mod policy_info;
mod query;
#[cfg(feature = "azure_policy")]
pub mod registry;
#[cfg(feature = "rvm")]
pub mod rvm;
mod scheduler;
#[cfg(feature = "azure_policy")]
mod schema;

View File

@@ -0,0 +1,280 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use super::{Instruction, InstructionData, LiteralOrRegister};
impl Instruction {
/// Get detailed display string with parameter resolution for debugging
pub fn display_with_params(&self, instruction_data: &InstructionData) -> String {
match self {
Instruction::LoopStart { params_index } => {
if let Some(params) = instruction_data.get_loop_params(*params_index) {
format!(
"LOOP_START {:?} R({}) R({}) R({}) R({}) {} {}",
params.mode,
params.collection,
params.key_reg,
params.value_reg,
params.result_reg,
params.body_start,
params.loop_end
)
} else {
format!("LOOP_START P({}) [INVALID INDEX]", params_index)
}
}
Instruction::BuiltinCall { params_index } => {
if let Some(params) = instruction_data.get_builtin_call_params(*params_index) {
let args_str = params
.arg_registers()
.iter()
.map(|&r| format!("R({})", r))
.collect::<Vec<_>>()
.join(" ");
format!(
"BUILTIN_CALL R({}) B({}) [{}]",
params.dest, params.builtin_index, args_str
)
} else {
format!("BUILTIN_CALL P({}) [INVALID INDEX]", params_index)
}
}
Instruction::HostAwait { dest, arg, id } => {
format!("HOST_AWAIT R({}) R({}) R({})", dest, arg, id)
}
Instruction::FunctionCall { params_index } => {
if let Some(params) = instruction_data.get_function_call_params(*params_index) {
let args_str = params
.arg_registers()
.iter()
.map(|&r| format!("R({})", r))
.collect::<Vec<_>>()
.join(" ");
format!(
"FUNCTION_CALL R({}) RULE({}) [{}]",
params.dest, params.func_rule_index, args_str
)
} else {
format!("FUNCTION_CALL P({}) [INVALID INDEX]", params_index)
}
}
Instruction::ObjectCreate { params_index } => {
if let Some(params) = instruction_data.get_object_create_params(*params_index) {
let mut field_parts = Vec::new();
// Add literal key fields
for &(literal_idx, value_reg) in params.literal_key_field_pairs() {
field_parts.push(format!("L({}):R({})", literal_idx, value_reg));
}
// Add non-literal key fields
for &(key_reg, value_reg) in params.field_pairs() {
field_parts.push(format!("R({}):R({})", key_reg, value_reg));
}
let fields_str = field_parts.join(" ");
format!(
"OBJECT_CREATE R({}) L({}) [{}]",
params.dest, params.template_literal_idx, fields_str
)
} else {
format!("OBJECT_CREATE P({}) [INVALID INDEX]", params_index)
}
}
Instruction::VirtualDataDocumentLookup { params_index } => {
if let Some(params) =
instruction_data.get_virtual_data_document_lookup_params(*params_index)
{
let components_str = params
.path_components
.iter()
.map(|comp| match comp {
LiteralOrRegister::Literal(idx) => format!("L({})", idx),
LiteralOrRegister::Register(reg) => format!("R({})", reg),
})
.collect::<Vec<_>>()
.join(".");
format!(
"VIRTUAL_DATA_DOCUMENT_LOOKUP R({}) [data.{}]",
params.dest, components_str
)
} else {
format!(
"VIRTUAL_DATA_DOCUMENT_LOOKUP P({}) [INVALID INDEX]",
params_index
)
}
}
Instruction::ComprehensionBegin { params_index } => {
if let Some(params) = instruction_data.get_comprehension_begin_params(*params_index)
{
format!(
"COMPREHENSION_BEGIN {:?} R({}) R({}) R({}) {} {}",
params.mode,
params.collection_reg,
params.key_reg,
params.value_reg,
params.body_start,
params.comprehension_end
)
} else {
format!("COMPREHENSION_BEGIN P({}) [INVALID INDEX]", params_index)
}
}
_ => self.to_string(),
}
}
}
impl core::fmt::Display for Instruction {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let text = match self {
Instruction::Load { dest, literal_idx } => {
format!("LOAD R({}) L({})", dest, literal_idx)
}
Instruction::LoadTrue { dest } => format!("LOAD_TRUE R({})", dest),
Instruction::LoadFalse { dest } => format!("LOAD_FALSE R({})", dest),
Instruction::LoadNull { dest } => format!("LOAD_NULL R({})", dest),
Instruction::LoadBool { dest, value } => format!("LOAD_BOOL R({}) {}", dest, value),
Instruction::LoadData { dest } => format!("LOAD_DATA R({})", dest),
Instruction::LoadInput { dest } => format!("LOAD_INPUT R({})", dest),
Instruction::Move { dest, src } => format!("MOVE R({}) R({})", dest, src),
Instruction::Add { dest, left, right } => {
format!("ADD R({}) R({}) R({})", dest, left, right)
}
Instruction::Sub { dest, left, right } => {
format!("SUB R({}) R({}) R({})", dest, left, right)
}
Instruction::Mul { dest, left, right } => {
format!("MUL R({}) R({}) R({})", dest, left, right)
}
Instruction::Div { dest, left, right } => {
format!("DIV R({}) R({}) R({})", dest, left, right)
}
Instruction::Mod { dest, left, right } => {
format!("MOD R({}) R({}) R({})", dest, left, right)
}
Instruction::Eq { dest, left, right } => {
format!("EQ R({}) R({}) R({})", dest, left, right)
}
Instruction::Ne { dest, left, right } => {
format!("NE R({}) R({}) R({})", dest, left, right)
}
Instruction::Lt { dest, left, right } => {
format!("LT R({}) R({}) R({})", dest, left, right)
}
Instruction::Le { dest, left, right } => {
format!("LE R({}) R({}) R({})", dest, left, right)
}
Instruction::Gt { dest, left, right } => {
format!("GT R({}) R({}) R({})", dest, left, right)
}
Instruction::Ge { dest, left, right } => {
format!("GE R({}) R({}) R({})", dest, left, right)
}
Instruction::And { dest, left, right } => {
format!("AND R({}) R({}) R({})", dest, left, right)
}
Instruction::Or { dest, left, right } => {
format!("OR R({}) R({}) R({})", dest, left, right)
}
Instruction::Not { dest, operand } => {
format!("NOT R({}) R({})", dest, operand)
}
Instruction::BuiltinCall { params_index } => {
format!("BUILTIN_CALL P({})", params_index)
}
Instruction::HostAwait { dest, arg, id } => {
format!("HOST_AWAIT R({}) R({}) R({})", dest, arg, id)
}
Instruction::FunctionCall { params_index } => {
format!("FUNCTION_CALL P({})", params_index)
}
Instruction::Return { value } => format!("RETURN R({})", value),
Instruction::ObjectSet { obj, key, value } => {
format!("OBJECT_SET R({}) R({}) R({})", obj, key, value)
}
Instruction::ObjectCreate { params_index } => {
format!("OBJECT_CREATE P({})", params_index)
}
Instruction::Index {
dest,
container,
key,
} => format!("INDEX R({}) R({}) R({})", dest, container, key),
Instruction::IndexLiteral {
dest,
container,
literal_idx,
} => format!(
"INDEX_LITERAL R({}) R({}) L({})",
dest, container, literal_idx
),
Instruction::ChainedIndex { params_index } => {
format!("CHAINED_INDEX P({})", params_index)
}
Instruction::ArrayNew { dest } => format!("ARRAY_NEW R({})", dest),
Instruction::ArrayPush { arr, value } => format!("ARRAY_PUSH R({}) R({})", arr, value),
Instruction::ArrayCreate { params_index } => {
format!("ARRAY_CREATE P({})", params_index)
}
Instruction::SetNew { dest } => format!("SET_NEW R({})", dest),
Instruction::SetAdd { set, value } => format!("SET_ADD R({}) R({})", set, value),
Instruction::SetCreate { params_index } => {
format!("SET_CREATE P({})", params_index)
}
Instruction::Contains {
dest,
collection,
value,
} => format!("CONTAINS R({}) R({}) R({})", dest, collection, value),
Instruction::Count { dest, collection } => {
format!("COUNT R({}) R({})", dest, collection)
}
Instruction::AssertCondition { condition } => {
format!("ASSERT_CONDITION R({})", condition)
}
Instruction::AssertNotUndefined { register } => {
format!("ASSERT_NOT_UNDEFINED R({})", register)
}
Instruction::LoopStart { params_index } => {
format!("LOOP_START P({})", params_index)
}
Instruction::LoopNext {
body_start,
loop_end,
} => {
format!("LOOP_NEXT {} {}", body_start, loop_end)
}
Instruction::CallRule { dest, rule_index } => {
format!("CALL_RULE R({}) {}", dest, rule_index)
}
Instruction::VirtualDataDocumentLookup { params_index } => {
format!("VIRTUAL_DATA_DOCUMENT_LOOKUP P({})", params_index)
}
Instruction::DestructuringSuccess {} => String::from("DESTRUCTURING_SUCCESS"),
Instruction::RuleReturn {} => String::from("RULE_RETURN"),
Instruction::RuleInit {
result_reg,
rule_index,
} => {
format!("RULE_INIT R({}) {}", result_reg, rule_index)
}
Instruction::Halt {} => String::from("HALT"),
Instruction::ComprehensionBegin { params_index } => {
format!("COMPREHENSION_BEGIN P({})", params_index)
}
Instruction::ComprehensionYield { value_reg, key_reg } => match key_reg {
Some(k) => format!("COMPREHENSION_YIELD R({}) R({})", k, value_reg),
None => format!("COMPREHENSION_YIELD R({})", value_reg),
},
Instruction::ComprehensionEnd {} => String::from("COMPREHENSION_END"),
};
write!(f, "{}", text)
}
}

381
src/rvm/instructions/mod.rs Normal file
View File

@@ -0,0 +1,381 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
mod display;
mod params;
mod types;
pub use params::{
ArrayCreateParams, BuiltinCallParams, ChainedIndexParams, ComprehensionBeginParams,
FunctionCallParams, InstructionData, LoopStartParams, ObjectCreateParams, SetCreateParams,
VirtualDataDocumentLookupParams,
};
pub use types::{ComprehensionMode, LiteralOrRegister, LoopMode};
use serde::{Deserialize, Serialize};
/// RVM Instructions - simplified enum-based design
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Instruction {
/// Load literal value from literal table into register
Load {
dest: u8,
literal_idx: u16,
},
/// Load true value into register
LoadTrue {
dest: u8,
},
/// Load false value into register
LoadFalse {
dest: u8,
},
/// Load null value into register
LoadNull {
dest: u8,
},
/// Load boolean value into register
LoadBool {
dest: u8,
value: bool,
},
/// Load global data object into register
LoadData {
dest: u8,
},
/// Load global input object into register
LoadInput {
dest: u8,
},
/// Move value from one register to another
Move {
dest: u8,
src: u8,
},
/// Arithmetic operations
Add {
dest: u8,
left: u8,
right: u8,
},
Sub {
dest: u8,
left: u8,
right: u8,
},
Mul {
dest: u8,
left: u8,
right: u8,
},
Div {
dest: u8,
left: u8,
right: u8,
},
Mod {
dest: u8,
left: u8,
right: u8,
},
/// Comparison operations
Eq {
dest: u8,
left: u8,
right: u8,
},
Ne {
dest: u8,
left: u8,
right: u8,
},
Lt {
dest: u8,
left: u8,
right: u8,
},
Le {
dest: u8,
left: u8,
right: u8,
},
Gt {
dest: u8,
left: u8,
right: u8,
},
Ge {
dest: u8,
left: u8,
right: u8,
},
/// Logical operations
And {
dest: u8,
left: u8,
right: u8,
},
Or {
dest: u8,
left: u8,
right: u8,
},
Not {
dest: u8,
operand: u8,
},
/// Builtin function calls - optimized for builtin functions
BuiltinCall {
/// Index into program's instruction_data.builtin_call_params table
params_index: u16,
},
/// Suspend execution and yield control to the host
HostAwait {
/// Destination register to store the resume value
dest: u8,
/// Register containing the value to pass to the host
arg: u8,
/// Register containing a unique identifier for this await site
id: u8,
},
/// Function rule calls - for user-defined function rules
FunctionCall {
/// Index into program's instruction_data.function_call_params table
params_index: u16,
},
/// Return result
Return {
value: u8,
},
/// Set object field
ObjectSet {
obj: u8,
key: u8,
value: u8,
},
/// Create object with optimized field setting - uses parameter table
ObjectCreate {
/// Index into program's instruction_data.object_create_params table
params_index: u16,
},
/// Index into container (object, array, set)
Index {
dest: u8,
container: u8,
key: u8,
},
/// Index into container using literal key (optimization for Load + Index)
IndexLiteral {
dest: u8,
container: u8,
literal_idx: u16,
},
/// Multi-level chained indexing (e.g., obj.field1[expr].field2)
ChainedIndex {
/// Index into program's instruction_data.chained_index_params table
params_index: u16,
},
/// Create empty array
ArrayNew {
dest: u8,
},
/// Push element to array
ArrayPush {
arr: u8,
value: u8,
},
/// Create array from registers - returns undefined if any element is undefined
ArrayCreate {
/// Index into program's instruction_data.array_create_params table
params_index: u16,
},
/// Create empty set
SetNew {
dest: u8,
},
/// Add element to set
SetAdd {
set: u8,
value: u8,
},
/// Create set from registers - returns undefined if any element is undefined
SetCreate {
/// Index into program's instruction_data.set_create_params table
params_index: u16,
},
/// Check if collection contains value (for membership testing)
Contains {
dest: u8,
collection: u8,
value: u8,
},
/// Get count/length of collection (arrays, objects, sets) - returns undefined for non-collections
Count {
dest: u8,
collection: u8,
},
/// Assert condition - if register contains false or undefined, return undefined immediately
AssertCondition {
condition: u8,
},
/// Assert not undefined - if register contains undefined, return undefined immediately
AssertNotUndefined {
register: u8,
},
/// Start a loop over a collection with specified semantics - uses parameter table
LoopStart {
/// Index into program's instruction_data.loop_params table
params_index: u16,
},
/// Continue to next iteration or exit loop
LoopNext {
/// Jump target back to loop body
body_start: u16,
/// Jump target for loop end
loop_end: u16,
},
/// Call rule with caching - checks cache first, executes rule if needed, supports call stack
CallRule {
/// Destination register to store the result of the rule call
dest: u8,
/// Rule index to execute
rule_index: u16,
},
/// Initialize a rule
RuleInit {
/// The register where rule's result is accumulated.
result_reg: u8,
/// The rule number of the rule
rule_index: u16,
},
/// Lookup in data namespace virtual documents (rules + base data)
VirtualDataDocumentLookup {
/// Index into program's instruction_data.virtual_data_document_lookup_params table
params_index: u16,
},
/// Mark successful completion of parameter destructuring validation
DestructuringSuccess {},
/// Return from rule execution
RuleReturn {},
/// Stop execution
Halt {},
/// Begin a comprehension with specified parameters
ComprehensionBegin {
/// Index into program's instruction_data.comprehension_begin_params table
params_index: u16,
},
/// Yield a value to the current comprehension result
ComprehensionYield {
/// Register containing the value to yield to the comprehension
value_reg: u8,
/// Optional register containing the key for object comprehensions
key_reg: Option<u8>,
},
/// End a comprehension block
ComprehensionEnd {},
}
impl Instruction {
/// Create a new LoopStart instruction with parameter table index
pub fn loop_start(params_index: u16) -> Self {
Self::LoopStart { params_index }
}
/// Create a new BuiltinCall instruction with parameter table index
pub fn builtin_call(params_index: u16) -> Self {
Self::BuiltinCall { params_index }
}
/// Create a new HostAwait instruction
pub fn host_await(dest: u8, arg: u8, id: u8) -> Self {
Self::HostAwait { dest, arg, id }
}
/// Create a new FunctionCall instruction with parameter table index
pub fn function_call(params_index: u16) -> Self {
Self::FunctionCall { params_index }
}
/// Create a new ObjectCreate instruction with parameter table index
pub fn object_create(params_index: u16) -> Self {
Self::ObjectCreate { params_index }
}
/// Create a new ArrayCreate instruction with parameter table index
pub fn array_create(params_index: u16) -> Self {
Self::ArrayCreate { params_index }
}
/// Create a new SetCreate instruction with parameter table index
pub fn set_create(params_index: u16) -> Self {
Self::SetCreate { params_index }
}
/// Create a new ComprehensionBegin instruction with parameter table index
pub fn comprehension_begin(params_index: u16) -> Self {
Self::ComprehensionBegin { params_index }
}
/// Create a new ComprehensionYield instruction
pub fn comprehension_yield(value_reg: u8) -> Self {
Self::ComprehensionYield {
value_reg,
key_reg: None,
}
}
/// Create a new ComprehensionYield instruction for object comprehensions
pub fn comprehension_yield_object(key_reg: u8, value_reg: u8) -> Self {
Self::ComprehensionYield {
value_reg,
key_reg: Some(key_reg),
}
}
/// Create a new ComprehensionEnd instruction
pub fn comprehension_end() -> Self {
Self::ComprehensionEnd {}
}
}

View File

@@ -0,0 +1,445 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use super::types::{ComprehensionMode, LiteralOrRegister, LoopMode};
/// Loop parameters stored in program's instruction data table
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoopStartParams {
/// Loop mode (Existential/Universal/Comprehension types)
pub mode: LoopMode,
/// Register containing the collection to iterate over
pub collection: u8,
/// Register to store current key (same as value_reg if key not needed)
pub key_reg: u8,
/// Register to store current value
pub value_reg: u8,
/// Register to store final result
pub result_reg: u8,
/// Jump target for loop body start
pub body_start: u16,
/// Jump target for loop end
pub loop_end: u16,
}
/// Builtin function call parameters stored in program's instruction data table
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuiltinCallParams {
/// Destination register to store the result
pub dest: u8,
/// Index into program's builtin_info_table
pub builtin_index: u16,
/// Number of arguments actually used
pub num_args: u8,
/// Argument register numbers (unused slots contain undefined values)
pub args: [u8; 8],
}
impl BuiltinCallParams {
/// Get the number of arguments actually used
pub fn arg_count(&self) -> usize {
self.num_args as usize
}
/// Get argument register numbers as a slice
pub fn arg_registers(&self) -> &[u8] {
&self.args[..self.num_args as usize]
}
}
/// Function rule call parameters stored in program's instruction data table
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionCallParams {
/// Destination register to store the result
pub dest: u8,
/// Rule index of the function to call
pub func_rule_index: u16,
/// Number of arguments actually used
pub num_args: u8,
/// Argument register numbers (unused slots contain undefined values)
pub args: [u8; 8],
}
impl FunctionCallParams {
/// Get the number of arguments actually used
pub fn arg_count(&self) -> usize {
self.num_args as usize
}
/// Get argument register numbers as a slice
pub fn arg_registers(&self) -> &[u8] {
&self.args[..self.num_args as usize]
}
}
/// Object creation parameters stored in program's instruction data table
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObjectCreateParams {
/// Destination register to store the result object
pub dest: u8,
/// Literal index of template object with all keys (undefined values)
/// Always present - empty object if no literal keys
pub template_literal_idx: u16,
/// Fields with literal keys: (literal_key_index, value_register) in sorted order
pub literal_key_fields: Vec<(u16, u8)>,
/// Fields with non-literal keys: (key_register, value_register)
pub fields: Vec<(u8, u8)>,
}
impl ObjectCreateParams {
/// Get the total number of fields
pub fn field_count(&self) -> usize {
self.literal_key_fields.len() + self.fields.len()
}
/// Get literal key field pairs as a slice
pub fn literal_key_field_pairs(&self) -> &[(u16, u8)] {
&self.literal_key_fields
}
/// Get non-literal key field pairs as a slice
pub fn field_pairs(&self) -> &[(u8, u8)] {
&self.fields
}
}
/// Array creation parameters stored in program's instruction data table
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArrayCreateParams {
/// Destination register to store the result array
pub dest: u8,
/// Register numbers containing the element values
pub elements: Vec<u8>,
}
impl ArrayCreateParams {
/// Get the number of elements
pub fn element_count(&self) -> usize {
self.elements.len()
}
/// Get element register numbers as a slice
pub fn element_registers(&self) -> &[u8] {
&self.elements
}
}
/// Set creation parameters stored in program's instruction data table
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SetCreateParams {
/// Destination register to store the result set
pub dest: u8,
/// Register numbers containing the element values
pub elements: Vec<u8>,
}
impl SetCreateParams {
/// Get the number of elements
pub fn element_count(&self) -> usize {
self.elements.len()
}
/// Get element register numbers as a slice
pub fn element_registers(&self) -> &[u8] {
&self.elements
}
}
/// Virtual data document lookup parameters for data namespace access with rule evaluation
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VirtualDataDocumentLookupParams {
/// Destination register to store the result
pub dest: u8,
/// Path components in order (e.g., for data.users[input.name].config)
/// This would be [Literal("users"), Register(5), Literal("config")]
/// where register 5 contains the value from input.name
pub path_components: Vec<LiteralOrRegister>,
}
impl VirtualDataDocumentLookupParams {
/// Get the number of path components
pub fn component_count(&self) -> usize {
self.path_components.len()
}
/// Check if all components are literals (can be optimized at compile time)
pub fn all_literals(&self) -> bool {
self.path_components
.iter()
.all(|c| matches!(c, LiteralOrRegister::Literal(_)))
}
/// Get just the literal indices (for debugging/display)
pub fn literal_indices(&self) -> Vec<u16> {
self.path_components
.iter()
.filter_map(|c| match c {
LiteralOrRegister::Literal(idx) => Some(*idx),
_ => None,
})
.collect()
}
/// Get just the register numbers (for debugging/display)
pub fn register_numbers(&self) -> Vec<u8> {
self.path_components
.iter()
.filter_map(|c| match c {
LiteralOrRegister::Register(reg) => Some(*reg),
_ => None,
})
.collect()
}
}
/// Chained index parameters for multi-level object access (input, locals, non-rule data paths)
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChainedIndexParams {
/// Destination register to store the result
pub dest: u8,
/// Root register containing the base object (input, local var, data subset)
pub root: u8,
/// Path components to traverse from the root
pub path_components: Vec<LiteralOrRegister>,
}
impl ChainedIndexParams {
/// Get the number of path components
pub fn component_count(&self) -> usize {
self.path_components.len()
}
/// Check if all components are literals (can be optimized)
pub fn all_literals(&self) -> bool {
self.path_components
.iter()
.all(|c| matches!(c, LiteralOrRegister::Literal(_)))
}
/// Get just the literal indices (for debugging/display)
pub fn literal_indices(&self) -> Vec<u16> {
self.path_components
.iter()
.filter_map(|c| match c {
LiteralOrRegister::Literal(idx) => Some(*idx),
_ => None,
})
.collect()
}
/// Get just the register numbers (for debugging/display)
pub fn register_numbers(&self) -> Vec<u8> {
self.path_components
.iter()
.filter_map(|c| match c {
LiteralOrRegister::Register(reg) => Some(*reg),
_ => None,
})
.collect()
}
}
/// Comprehension parameters stored in program's instruction data table
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComprehensionBeginParams {
/// Type of comprehension being created
pub mode: ComprehensionMode,
/// Register containing the source collection to iterate over
pub collection_reg: u8,
/// Register to store the comprehension result collection
/// If not specified separately, this will match collection_reg
pub result_reg: u8,
/// Register to store current iteration key
pub key_reg: u8,
/// Register to store current iteration value
pub value_reg: u8,
/// Jump target for comprehension body start
pub body_start: u16,
/// Jump target for comprehension end
pub comprehension_end: u16,
}
/// Instruction data container for complex instruction parameters
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InstructionData {
/// Loop parameter table for LoopStart instructions
pub loop_params: Vec<LoopStartParams>,
/// Builtin function call parameter table for BuiltinCall instructions
pub builtin_call_params: Vec<BuiltinCallParams>,
/// Function rule call parameter table for FunctionCall instructions
pub function_call_params: Vec<FunctionCallParams>,
/// Object creation parameter table for ObjectCreate instructions
pub object_create_params: Vec<ObjectCreateParams>,
/// Array creation parameter table for ArrayCreate instructions
pub array_create_params: Vec<ArrayCreateParams>,
/// Set creation parameter table for SetCreate instructions
pub set_create_params: Vec<SetCreateParams>,
/// Virtual data document lookup parameter table for VirtualDataDocumentLookup instructions
pub virtual_data_document_lookup_params: Vec<VirtualDataDocumentLookupParams>,
/// Chained index parameter table for ChainedIndex instructions
pub chained_index_params: Vec<ChainedIndexParams>,
/// Comprehension parameter table for ComprehensionBegin instructions
pub comprehension_begin_params: Vec<ComprehensionBeginParams>,
}
impl InstructionData {
/// Create a new empty instruction data container
pub fn new() -> Self {
Self {
loop_params: Vec::new(),
builtin_call_params: Vec::new(),
function_call_params: Vec::new(),
object_create_params: Vec::new(),
array_create_params: Vec::new(),
set_create_params: Vec::new(),
virtual_data_document_lookup_params: Vec::new(),
chained_index_params: Vec::new(),
comprehension_begin_params: Vec::new(),
}
}
/// Add loop parameters and return the index
pub fn add_loop_params(&mut self, params: LoopStartParams) -> u16 {
let index = self.loop_params.len();
self.loop_params.push(params);
index as u16
}
/// Add builtin call parameters and return the index
pub fn add_builtin_call_params(&mut self, params: BuiltinCallParams) -> u16 {
let index = self.builtin_call_params.len();
self.builtin_call_params.push(params);
index as u16
}
/// Add function call parameters and return the index
pub fn add_function_call_params(&mut self, params: FunctionCallParams) -> u16 {
let index = self.function_call_params.len();
self.function_call_params.push(params);
index as u16
}
/// Add object create parameters and return the index
pub fn add_object_create_params(&mut self, params: ObjectCreateParams) -> u16 {
let index = self.object_create_params.len();
self.object_create_params.push(params);
index as u16
}
/// Add array create parameters and return the index
pub fn add_array_create_params(&mut self, params: ArrayCreateParams) -> u16 {
let index = self.array_create_params.len();
self.array_create_params.push(params);
index as u16
}
/// Add set create parameters and return the index
pub fn add_set_create_params(&mut self, params: SetCreateParams) -> u16 {
let index = self.set_create_params.len();
self.set_create_params.push(params);
index as u16
}
/// Get loop parameters by index
pub fn get_loop_params(&self, index: u16) -> Option<&LoopStartParams> {
self.loop_params.get(index as usize)
}
/// Get builtin call parameters by index
pub fn get_builtin_call_params(&self, index: u16) -> Option<&BuiltinCallParams> {
self.builtin_call_params.get(index as usize)
}
/// Get function call parameters by index
pub fn get_function_call_params(&self, index: u16) -> Option<&FunctionCallParams> {
self.function_call_params.get(index as usize)
}
/// Get object create parameters by index
pub fn get_object_create_params(&self, index: u16) -> Option<&ObjectCreateParams> {
self.object_create_params.get(index as usize)
}
/// Get array create parameters by index
pub fn get_array_create_params(&self, index: u16) -> Option<&ArrayCreateParams> {
self.array_create_params.get(index as usize)
}
/// Get set create parameters by index
pub fn get_set_create_params(&self, index: u16) -> Option<&SetCreateParams> {
self.set_create_params.get(index as usize)
}
/// Add virtual data document lookup parameters and return the index
pub fn add_virtual_data_document_lookup_params(
&mut self,
params: VirtualDataDocumentLookupParams,
) -> u16 {
let index = self.virtual_data_document_lookup_params.len();
self.virtual_data_document_lookup_params.push(params);
index as u16
}
/// Get virtual data document lookup parameters by index
pub fn get_virtual_data_document_lookup_params(
&self,
index: u16,
) -> Option<&VirtualDataDocumentLookupParams> {
self.virtual_data_document_lookup_params.get(index as usize)
}
/// Add chained index parameters and return the index
pub fn add_chained_index_params(&mut self, params: ChainedIndexParams) -> u16 {
let index = self.chained_index_params.len();
self.chained_index_params.push(params);
index as u16
}
/// Get chained index parameters by index
pub fn get_chained_index_params(&self, index: u16) -> Option<&ChainedIndexParams> {
self.chained_index_params.get(index as usize)
}
/// Get mutable reference to loop parameters by index
pub fn get_loop_params_mut(&mut self, index: u16) -> Option<&mut LoopStartParams> {
self.loop_params.get_mut(index as usize)
}
/// Add comprehension begin parameters and return the index
pub fn add_comprehension_begin_params(&mut self, params: ComprehensionBeginParams) -> u16 {
let index = self.comprehension_begin_params.len();
self.comprehension_begin_params.push(params);
index as u16
}
/// Get comprehension begin parameters by index
pub fn get_comprehension_begin_params(&self, index: u16) -> Option<&ComprehensionBeginParams> {
self.comprehension_begin_params.get(index as usize)
}
/// Get mutable reference to comprehension begin parameters by index
pub fn get_comprehension_begin_params_mut(
&mut self,
index: u16,
) -> Option<&mut ComprehensionBeginParams> {
self.comprehension_begin_params.get_mut(index as usize)
}
}
impl Default for InstructionData {
fn default() -> Self {
Self::new()
}
}

View File

@@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use serde::{Deserialize, Serialize};
/// Represents either a literal index or a register number for path components
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum LiteralOrRegister {
/// Index into the program's literal table
Literal(u16),
/// Register number containing the value
Register(u8),
}
/// Loop execution modes for different Rego iteration constructs
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum LoopMode {
/// Any quantification: some x in arr, x := arr[_], etc.
/// Succeeds if ANY iteration succeeds, exits early on first success
Any,
/// Every quantification: every x in arr
/// Succeeds only if ALL iterations succeed, exits early on first failure
Every,
/// ForEach processing: processes all elements without early exit
/// Used for set membership rules (contains), object rules, and complete rules
/// where all candidates must be evaluated. Determined by output constness.
ForEach,
}
/// Comprehension execution modes for different comprehension types
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ComprehensionMode {
/// Set comprehension: {expr | condition}
/// Collects unique successful iterations into a set
Set,
/// Array comprehension: [expr | condition]
/// Collects successful iterations into an array (preserves order)
Array,
/// Object comprehension: {key: value | condition}
/// Collects successful key-value pairs into an object
Object,
}

16
src/rvm/mod.rs Normal file
View File

@@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// RVM - Rego Virtual Machine
// A register-based virtual machine for executing Rego policies
pub mod instructions;
pub mod program;
pub mod tests;
pub mod vm;
pub use instructions::Instruction;
pub use program::{
generate_assembly_listing, generate_tabular_assembly_listing, AssemblyListingConfig, Program,
};
pub use vm::RegoVM;

322
src/rvm/program/core.rs Normal file
View File

@@ -0,0 +1,322 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use anyhow::Result as AnyResult;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use super::types::{BuiltinInfo, ProgramMetadata, RuleInfo, SourceFile, SpanInfo};
use crate::builtins::BuiltinFcn;
use crate::rvm::instructions::InstructionData;
use crate::rvm::Instruction;
use crate::value::Value;
/// Complete compiled program containing all execution artifacts
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Program {
/// Compiled bytecode instructions
pub instructions: Vec<Instruction>,
/// Literal value table (skipped in serde, serialized separately as JSON)
#[serde(skip, default = "Vec::new")]
pub literals: Vec<Value>,
/// Complex instruction parameter data (for LoopStart, Call, etc.)
pub instruction_data: InstructionData,
/// Builtin function information table
pub builtin_info_table: Vec<BuiltinInfo>,
/// Entry points mapping with preserved insertion order (skipped in serde, serialized separately as JSON)
#[serde(skip, default = "IndexMap::new")]
pub entry_points: IndexMap<String, usize>,
/// Source files table with content (skipped in serde, serialized separately as JSON)
#[serde(skip, default = "Vec::new")]
pub sources: Vec<SourceFile>,
/// Rule metadata: rule_index -> rule information
pub rule_infos: Vec<RuleInfo>,
/// Span information for each instruction (for debugging)
pub instruction_spans: Vec<Option<SpanInfo>>,
/// Main program entry point
pub main_entry_point: usize,
/// Maximum register window size observed across all rule definitions
pub max_rule_window_size: usize,
/// Register window size needed for entry point dispatch
pub dispatch_window_size: usize,
/// Program metadata
pub metadata: ProgramMetadata,
/// Rule tree for efficient rule lookup (skipped in serde, serialized separately as JSON)
/// Maps rule paths (e.g., "data.p1.r1") to rule indices
/// Structure: {"p1": {"r1": rule_index}, "p2": {"p3": {"r2": rule_index}}}
#[serde(skip, default = "Value::new_object")]
pub rule_tree: Value,
/// Resolved builtins - actual builtin function values fetched from interpreter's builtin map
/// This field is skipped during serialization and reinitialized after deserialization
#[serde(skip)]
pub resolved_builtins: Vec<BuiltinFcn>,
/// Flag indicating that VirtualDataDocumentLookup instruction was used and runtime recursion checking is needed
pub needs_runtime_recursion_check: bool,
/// Flag indicating that recompilation is needed due to partial deserialization failure
/// This is set to true when the artifact section was successfully read but the extensible
/// section failed to deserialize (e.g., due to version incompatibility)
#[serde(default)]
pub needs_recompilation: bool,
/// Rego language version used for compilation (true for Rego v0, false for Rego v1)
/// This must be preserved during recompilation to maintain policy semantics
/// Serialized separately in the artifact section for guaranteed availability
#[serde(skip, default)]
pub rego_v0: bool,
}
impl Program {
/// Current serialization format version
pub const SERIALIZATION_VERSION: u32 = 3;
/// Magic bytes to identify Regorus program files
pub const MAGIC: [u8; 4] = *b"REGO";
/// Create a new empty program
pub fn new() -> Self {
Self {
instructions: Vec::new(),
literals: Vec::new(),
instruction_data: InstructionData::new(),
builtin_info_table: Vec::new(),
entry_points: IndexMap::new(),
sources: Vec::new(),
rule_infos: Vec::new(),
instruction_spans: Vec::new(),
main_entry_point: 0,
max_rule_window_size: 0,
dispatch_window_size: 0,
metadata: ProgramMetadata {
compiler_version: env!("CARGO_PKG_VERSION").to_string(),
compiled_at: "unknown".to_string(),
source_info: "unknown".to_string(),
optimization_level: 0,
},
rule_tree: Value::new_object(),
resolved_builtins: Vec::new(),
needs_runtime_recursion_check: false,
needs_recompilation: false,
rego_v0: false, // Default to Rego v1
}
}
/// Add a source file and return its index
pub fn add_source(&mut self, name: String, content: String) -> usize {
let source_file = SourceFile::new(name.clone(), content);
let index = self.sources.len();
self.sources.push(source_file);
index
}
/// Add loop parameters and return the index
pub fn add_loop_params(&mut self, params: crate::rvm::instructions::LoopStartParams) -> u16 {
self.instruction_data.add_loop_params(params)
}
/// Add comprehension begin parameters and return the index
pub fn add_comprehension_begin_params(
&mut self,
params: crate::rvm::instructions::ComprehensionBeginParams,
) -> u16 {
self.instruction_data.add_comprehension_begin_params(params)
}
/// Add builtin call parameters and return the index
pub fn add_builtin_call_params(
&mut self,
params: crate::rvm::instructions::BuiltinCallParams,
) -> u16 {
self.instruction_data.add_builtin_call_params(params)
}
/// Add function call parameters and return the index
pub fn add_function_call_params(
&mut self,
params: crate::rvm::instructions::FunctionCallParams,
) -> u16 {
self.instruction_data.add_function_call_params(params)
}
/// Add builtin info and return the index
pub fn add_builtin_info(&mut self, builtin_info: BuiltinInfo) -> u16 {
let index = self.builtin_info_table.len();
self.builtin_info_table.push(builtin_info);
index as u16
}
/// Get builtin info by index
pub fn get_builtin_info(&self, index: u16) -> Option<&BuiltinInfo> {
self.builtin_info_table.get(index as usize)
}
/// Update loop parameters by index
pub fn update_loop_params<F>(&mut self, params_index: u16, updater: F)
where
F: FnOnce(&mut crate::rvm::instructions::LoopStartParams),
{
if let Some(params) = self.instruction_data.get_loop_params_mut(params_index) {
updater(params);
}
}
/// Update comprehension begin parameters by index
pub fn update_comprehension_begin_params<F>(&mut self, params_index: u16, updater: F)
where
F: FnOnce(&mut crate::rvm::instructions::ComprehensionBeginParams),
{
if let Some(params) = self
.instruction_data
.get_comprehension_begin_params_mut(params_index)
{
updater(params);
}
}
/// Get detailed instruction display with parameter resolution
pub fn display_instruction_with_params(&self, instruction: &Instruction) -> String {
instruction.display_with_params(&self.instruction_data)
}
/// Add a source file directly and return its index
pub fn add_source_file(&mut self, source_file: SourceFile) -> usize {
for (i, existing) in self.sources.iter().enumerate() {
if existing.name == source_file.name {
return i;
}
}
let index = self.sources.len();
self.sources.push(source_file);
index
}
/// Get source file by index
pub fn get_source_file(&self, index: usize) -> Option<&SourceFile> {
self.sources.get(index)
}
/// Get source content by index
pub fn get_source(&self, index: usize) -> Option<&str> {
self.sources.get(index).map(|s| s.content.as_str())
}
/// Get source name by index
pub fn get_source_name(&self, index: usize) -> Option<&str> {
self.sources.get(index).map(|s| s.name.as_str())
}
/// Get rule info by index
pub fn get_rule_info(&self, rule_index: usize) -> Option<&RuleInfo> {
self.rule_infos.get(rule_index)
}
/// Get span information for instruction
pub fn get_instruction_span(&self, instruction_index: usize) -> Option<&SpanInfo> {
self.instruction_spans
.get(instruction_index)
.and_then(|span| span.as_ref())
}
/// Add instruction with optional span
pub fn add_instruction(&mut self, instruction: Instruction, span: Option<SpanInfo>) {
self.instructions.push(instruction);
self.instruction_spans.push(span);
}
/// Add literal value and return its index
pub fn add_literal(&mut self, value: Value) -> usize {
for (i, existing) in self.literals.iter().enumerate() {
if existing == &value {
return i;
}
}
let index = self.literals.len();
self.literals.push(value);
index
}
/// Initialize resolved builtins directly from the BUILTINS HashMap
/// This should be called after deserialization to populate the skipped field
/// Returns an error if any required builtin is missing
pub fn initialize_resolved_builtins(&mut self) -> AnyResult<()> {
self.resolved_builtins.clear();
self.resolved_builtins
.reserve(self.builtin_info_table.len());
for builtin_info in &self.builtin_info_table {
if let Some(&builtin_fcn) = crate::builtins::BUILTINS.get(builtin_info.name.as_str()) {
self.resolved_builtins.push(builtin_fcn);
} else {
return Err(anyhow::anyhow!(
"Missing builtin function: {}",
builtin_info.name
));
}
}
Ok(())
}
/// Get resolved builtin function by index
pub fn get_resolved_builtin(&self, index: u16) -> Option<&BuiltinFcn> {
self.resolved_builtins.get(index as usize)
}
/// Check if resolved builtins are initialized
pub fn has_resolved_builtins(&self) -> bool {
!self.resolved_builtins.is_empty()
}
/// Add an entry point mapping from path to rule index
pub fn add_entry_point(&mut self, path: String, rule_index: usize) {
self.entry_points.insert(path, rule_index);
}
/// Get rule index for an entry point path
pub fn get_entry_point(&self, path: &str) -> Option<usize> {
self.entry_points.get(path).copied()
}
/// Get all entry points as IndexMap
pub fn get_entry_points(&self) -> &IndexMap<String, usize> {
&self.entry_points
}
/// Check if recompilation is needed due to partial deserialization failure
pub fn needs_recompilation(&self) -> bool {
self.needs_recompilation
}
/// Mark that recompilation is needed
pub fn set_needs_recompilation(&mut self, needs_recompilation: bool) {
self.needs_recompilation = needs_recompilation;
}
/// Check if the program is fully functional (not needing recompilation)
pub fn is_fully_functional(&self) -> bool {
!self.needs_recompilation
}
}
impl Default for Program {
fn default() -> Self {
Self::new()
}
}

964
src/rvm/program/listing.rs Normal file
View File

@@ -0,0 +1,964 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt::Write;
use crate::rvm::{
instructions::{Instruction, InstructionData, LoopMode},
program::Program,
};
/// Configuration for assembly listing output
#[derive(Debug, Clone)]
pub struct AssemblyListingConfig {
/// Show instruction addresses
pub show_addresses: bool,
/// Show raw instruction bytes (if available)
pub show_bytes: bool,
/// Indent size for nested loops
pub indent_size: usize,
/// Maximum width for instruction column
pub instruction_width: usize,
/// Show literal values inline
pub show_literal_values: bool,
/// Column position for comments
pub comment_column: usize,
}
impl Default for AssemblyListingConfig {
fn default() -> Self {
Self {
show_addresses: true,
show_bytes: false,
indent_size: 4,
instruction_width: 40,
show_literal_values: true,
comment_column: 50,
}
}
}
/// Generate annotated assembly listing for a compiled program
pub fn generate_assembly_listing(program: &Program, config: &AssemblyListingConfig) -> String {
let mut output = String::new();
let mut indent_level: usize = 0;
let mut current_rule_index: Option<u16> = None;
// Track active loops and comprehensions by their end addresses
let mut active_ends: Vec<u16> = Vec::new();
// Add header
writeln!(
output,
"; RVM Assembly - {} instructions, {} literals, {} builtins",
program.instructions.len(),
program.literals.len(),
program.builtin_info_table.len()
)
.unwrap();
// Add builtins table
if !program.builtin_info_table.is_empty() {
writeln!(output, ";").unwrap();
writeln!(output, "; BUILTINS TABLE:").unwrap();
for (idx, builtin_info) in program.builtin_info_table.iter().enumerate() {
writeln!(output, "; B{:2}: {}", idx, builtin_info.name).unwrap();
}
}
// Add literals table
if config.show_literal_values && !program.literals.is_empty() {
writeln!(output, ";").unwrap();
writeln!(output, "; LITERALS (JSON values):").unwrap();
for (idx, literal) in program.literals.iter().enumerate() {
let literal_json =
serde_json::to_string(literal).unwrap_or_else(|_| "<invalid>".to_string());
writeln!(output, "; L{:2}: {}", idx, literal_json).unwrap();
}
}
// Add rules table if available
if !program.rule_infos.is_empty() {
writeln!(output, ";").unwrap();
writeln!(output, "; RULES TABLE:").unwrap();
for (idx, rule_info) in program.rule_infos.iter().enumerate() {
writeln!(output, "; R{:2}: {}", idx, rule_info.name).unwrap();
}
}
writeln!(output, ";").unwrap();
for (pc, instruction) in program.instructions.iter().enumerate() {
// Handle rule transitions and add gaps
if let Instruction::RuleInit { rule_index, .. } = instruction {
// Add gap before new rule (except for the first rule)
if current_rule_index.is_some() {
writeln!(output).unwrap();
}
current_rule_index = Some(*rule_index);
// Add rule name prefix
if let Some(rule_info) = program.rule_infos.get(*rule_index as usize) {
writeln!(output, "; ===== RULE: {} =====", rule_info.name).unwrap();
} else {
writeln!(output, "; ===== RULE: rule_{} =====", rule_index).unwrap();
}
}
// Check if current PC matches any active end addresses (loops, comprehensions, rules)
let current_pc = pc as u16;
while let Some(&end_addr) = active_ends.last() {
if current_pc >= end_addr {
active_ends.pop();
indent_level = indent_level.saturating_sub(1);
} else {
break;
}
}
// Handle explicit end instructions
match instruction {
Instruction::LoopNext { .. } => {
// LoopNext already handled by end address tracking above
}
Instruction::RuleReturn { .. } => {
indent_level = indent_level.saturating_sub(1);
}
_ => {}
}
// Special case: Block end instructions should be indented at their block level (one level out)
let effective_indent_level = match instruction {
Instruction::ComprehensionEnd {} => indent_level.saturating_sub(1),
Instruction::LoopNext { .. } => indent_level.saturating_sub(1),
_ => indent_level,
};
let indent = " ".repeat(effective_indent_level * config.indent_size);
// Format address
let addr_str = if config.show_addresses {
format!("{:03}: ", pc)
} else {
String::new()
};
// Format instruction with proper indentation and aligned comments
let inst_str = format_instruction_readable(
instruction,
&indent,
&program.instruction_data,
program,
config,
);
writeln!(output, "{}{}", addr_str, inst_str).unwrap();
// Increase indentation for loop/rule/comprehension starts and track their end addresses
match instruction {
Instruction::LoopStart { params_index } => {
if let Some(params) = program.instruction_data.get_loop_params(*params_index) {
active_ends.push(params.loop_end);
indent_level += 1;
}
}
Instruction::ComprehensionBegin { params_index } => {
if let Some(params) = program
.instruction_data
.get_comprehension_begin_params(*params_index)
{
active_ends.push(params.comprehension_end);
indent_level += 1;
}
}
Instruction::RuleInit { .. } => {
indent_level += 1;
// Note: Rules end with RuleReturn, not an address, so we don't track them here
}
_ => {}
}
}
output
}
/// Helper function to align comments at a specific column
fn align_comment(base_text: &str, comment: &str, target_column: usize) -> String {
let current_len = base_text.len();
if current_len >= target_column {
format!("{} ; {}", base_text, comment)
} else {
let padding = " ".repeat(target_column - current_len);
format!("{}{} ; {}", base_text, padding, comment)
}
}
/// Format a single instruction with proper indentation and mathematical notation
fn format_instruction_readable(
instruction: &Instruction,
indent: &str,
instruction_data: &InstructionData,
program: &Program,
config: &AssemblyListingConfig,
) -> String {
match instruction {
Instruction::Load { dest, literal_idx } => {
let base = format!("{}Load r{} ← L{}", indent, dest, literal_idx);
let comment = if *literal_idx < program.literals.len() as u16 {
let literal_json = serde_json::to_string(&program.literals[*literal_idx as usize])
.unwrap_or_else(|_| "<invalid>".to_string());
format!("Load literal: {}", literal_json)
} else {
"Load literal: <invalid index>".to_string()
};
align_comment(&base, &comment, config.comment_column)
}
Instruction::LoadTrue { dest } => {
let base = format!("{}LoadTrue r{} ← true", indent, dest);
align_comment(&base, "Load boolean constant true", config.comment_column)
}
Instruction::LoadFalse { dest } => {
let base = format!("{}LoadFalse r{} ← false", indent, dest);
align_comment(&base, "Load boolean constant false", config.comment_column)
}
Instruction::LoadNull { dest } => {
let base = format!("{}LoadNull r{} ← null", indent, dest);
align_comment(&base, "Load null value", config.comment_column)
}
Instruction::LoadBool { dest, value } => {
let base = format!("{}LoadBool r{}{}", indent, dest, value);
let comment = format!("Load boolean constant {}", value);
align_comment(&base, &comment, config.comment_column)
}
Instruction::LoadData { dest } => {
let base = format!("{}LoadData r{} ← data", indent, dest);
align_comment(&base, "Load global data document", config.comment_column)
}
Instruction::LoadInput { dest } => {
let base = format!("{}LoadInput r{} ← input", indent, dest);
align_comment(&base, "Load global input document", config.comment_column)
}
Instruction::Move { dest, src } => {
let base = format!("{}Move r{} ← r{}", indent, dest, src);
let comment = format!("Copy value from r{} to r{}", src, dest);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Add { dest, left, right } => {
let base = format!("{}Add r{} ← r{} + r{}", indent, dest, left, right);
let comment = format!("Arithmetic addition: r{} + r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Sub { dest, left, right } => {
let base = format!("{}Sub r{} ← r{} - r{}", indent, dest, left, right);
let comment = format!("Arithmetic subtraction: r{} - r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Mul { dest, left, right } => {
let base = format!("{}Mul r{} ← r{} × r{}", indent, dest, left, right);
let comment = format!("Arithmetic multiplication: r{} × r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Div { dest, left, right } => {
let base = format!("{}Div r{} ← r{} ÷ r{}", indent, dest, left, right);
let comment = format!("Arithmetic division: r{} ÷ r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Mod { dest, left, right } => {
let base = format!(
"{}Mod r{} ← r{} mod r{}",
indent, dest, left, right
);
let comment = format!("Modulo operation: r{} mod r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Eq { dest, left, right } => {
let base = format!(
"{}Eq r{} ← (r{} = r{})",
indent, dest, left, right
);
let comment = format!("Equality test: r{} == r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Ne { dest, left, right } => {
let base = format!(
"{}Ne r{} ← (r{} ≠ r{})",
indent, dest, left, right
);
let comment = format!("Inequality test: r{} != r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Lt { dest, left, right } => {
let base = format!(
"{}Lt r{} ← (r{} < r{})",
indent, dest, left, right
);
let comment = format!("Less than comparison: r{} < r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Le { dest, left, right } => {
let base = format!(
"{}Le r{} ← (r{} ≤ r{})",
indent, dest, left, right
);
let comment = format!("Less or equal comparison: r{} <= r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Gt { dest, left, right } => {
let base = format!(
"{}Gt r{} ← (r{} > r{})",
indent, dest, left, right
);
let comment = format!("Greater than comparison: r{} > r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Ge { dest, left, right } => {
let base = format!(
"{}Ge r{} ← (r{} ≥ r{})",
indent, dest, left, right
);
let comment = format!("Greater or equal comparison: r{} >= r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::And { dest, left, right } => {
let base = format!("{}And r{} ← r{} ∧ r{}", indent, dest, left, right);
let comment = format!("Logical AND: r{} && r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Or { dest, left, right } => {
let base = format!("{}Or r{} ← r{} r{}", indent, dest, left, right);
let comment = format!("Logical OR: r{} || r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Not { dest, operand } => {
let base = format!("{}Not r{} ← ¬r{}", indent, dest, operand);
let comment = format!("Logical NOT: !r{}", operand);
align_comment(&base, &comment, config.comment_column)
}
Instruction::BuiltinCall { params_index } => {
if let Some(params) = instruction_data.get_builtin_call_params(*params_index) {
let args_str = params
.arg_registers()
.iter()
.map(|&r| format!("r{}", r))
.collect::<Vec<_>>()
.join(", ");
let builtin_name = program
.builtin_info_table
.get(params.builtin_index as usize)
.map(|info| info.name.as_str())
.unwrap_or("<invalid>");
let base = format!(
"{}BuiltinCall r{}{}({})",
indent, params.dest, builtin_name, args_str
);
let comment = format!(
"Call builtin '{}' (B{}) with {} args",
builtin_name, params.builtin_index, params.num_args
);
align_comment(&base, &comment, config.comment_column)
} else {
let base = format!("{}BuiltinCall [INVALID P({})]", indent, params_index);
align_comment(
&base,
"ERROR: Invalid builtin call parameters",
config.comment_column,
)
}
}
Instruction::FunctionCall { params_index } => {
if let Some(params) = instruction_data.get_function_call_params(*params_index) {
let args_str = params
.arg_registers()
.iter()
.map(|&r| format!("r{}", r))
.collect::<Vec<_>>()
.join(", ");
let func_name = program
.rule_infos
.get(params.func_rule_index as usize)
.map(|info| info.name.as_str())
.unwrap_or("<invalid>");
let base = format!(
"{}FunctionCall r{}{}({})",
indent, params.dest, func_name, args_str
);
let comment = format!(
"Call function '{}' (R{}) with {} args",
func_name, params.func_rule_index, params.num_args
);
align_comment(&base, &comment, config.comment_column)
} else {
let base = format!("{}FunctionCall [INVALID P({})]", indent, params_index);
align_comment(
&base,
"ERROR: Invalid function call parameters",
config.comment_column,
)
}
}
Instruction::HostAwait { dest, arg, id } => {
let base = format!(
"{}HostAwait r{} ← await r{} (id r{})",
indent, dest, arg, id
);
align_comment(
&base,
&format!(
"Suspend and request host result using r{} with identifier r{}",
arg, id
),
config.comment_column,
)
}
Instruction::Return { value } => {
let base = format!("{}Return return r{}", indent, value);
let comment = format!("Return value from r{}", value);
align_comment(&base, &comment, config.comment_column)
}
Instruction::ObjectSet { obj, key, value } => {
let base = format!("{}ObjectSet r{}[r{}] ← r{}", indent, obj, key, value);
let comment = format!("Set field r{}[r{}] = r{}", obj, key, value);
align_comment(&base, &comment, config.comment_column)
}
Instruction::ObjectCreate { params_index } => {
let params = program
.instruction_data
.get_object_create_params(*params_index);
let base = format!(
"{}ObjectCreate r{}{{...}}",
indent,
params.map_or(0, |p| p.dest)
);
let comment = match params {
Some(p) => format!(
"Create object with {} fields (P{})",
p.field_count(),
params_index
),
None => format!("Create object (P{} - INVALID)", params_index),
};
align_comment(&base, &comment, config.comment_column)
}
Instruction::Index {
dest,
container,
key,
} => {
let base = format!(
"{}Index r{} ← r{}[r{}]",
indent, dest, container, key
);
let comment = format!("Index operation: get r{}[r{}]", container, key);
align_comment(&base, &comment, config.comment_column)
}
Instruction::IndexLiteral {
dest,
container,
literal_idx,
} => {
let base = format!(
"{}IndexLiteral r{} ← r{}[L{}]",
indent, dest, container, literal_idx
);
let comment = if *literal_idx < program.literals.len() as u16 {
let literal_json = serde_json::to_string(&program.literals[*literal_idx as usize])
.unwrap_or_else(|_| "<invalid>".to_string());
format!("Index with literal key: r{}[{}]", container, literal_json)
} else {
format!(
"Index with literal: r{}[L{}] (invalid index)",
container, literal_idx
)
};
align_comment(&base, &comment, config.comment_column)
}
Instruction::ArrayNew { dest } => {
let base = format!("{}ArrayNew r{} ← []", indent, dest);
align_comment(&base, "Create new empty array", config.comment_column)
}
Instruction::ArrayPush { arr, value } => {
let base = format!("{}ArrayPush r{}.push(r{})", indent, arr, value);
let comment = format!("Append r{} to array r{}", value, arr);
align_comment(&base, &comment, config.comment_column)
}
Instruction::ArrayCreate { params_index } => {
if let Some(params) = instruction_data.get_array_create_params(*params_index) {
let elements = params
.element_registers()
.iter()
.map(|r| format!("r{}", r))
.collect::<Vec<_>>()
.join(", ");
let base = format!("{}ArrayCreate r{} ← [{}]", indent, params.dest, elements);
let comment = format!(
"Create array from {} elements (undefined if any element is undefined)",
params.element_count()
);
align_comment(&base, &comment, config.comment_column)
} else {
format!("{}ArrayCreate <invalid params P{}>", indent, params_index)
}
}
Instruction::SetNew { dest } => {
let base = format!("{}SetNew r{} ← set()", indent, dest);
align_comment(&base, "Create new empty set", config.comment_column)
}
Instruction::SetAdd { set, value } => {
let base = format!("{}SetAdd r{} = r{}", indent, set, value);
let comment = format!("Add r{} to set r{}", value, set);
align_comment(&base, &comment, config.comment_column)
}
Instruction::SetCreate { params_index } => {
if let Some(params) = instruction_data.get_set_create_params(*params_index) {
let elements = params
.element_registers()
.iter()
.map(|r| format!("r{}", r))
.collect::<Vec<_>>()
.join(", ");
let base = format!("{}SetCreate r{}{{{}}}", indent, params.dest, elements);
let comment = format!(
"Create set from {} elements (undefined if any element is undefined)",
params.element_count()
);
align_comment(&base, &comment, config.comment_column)
} else {
format!("{}SetCreate <invalid params P{}>", indent, params_index)
}
}
Instruction::Contains {
dest,
collection,
value,
} => {
let base = format!(
"{}Contains r{} ← (r{} ∈ r{})",
indent, dest, value, collection
);
let comment = format!("Membership test: r{} in r{}", value, collection);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Count { dest, collection } => {
let base = format!("{}Count r{} ← count(r{})", indent, dest, collection);
let comment = format!("Get count/length of collection r{}", collection);
align_comment(&base, &comment, config.comment_column)
}
Instruction::AssertCondition { condition } => {
let base = format!("{}Assert assert r{}", indent, condition);
let comment = format!("Assert r{} is true (exit if false/undefined)", condition);
align_comment(&base, &comment, config.comment_column)
}
Instruction::AssertNotUndefined { register } => {
let base = format!(
"{}AssertNotUndefined assert_not_undefined r{}",
indent, register
);
let comment = format!("Assert r{} is not undefined (exit if undefined)", register);
align_comment(&base, &comment, config.comment_column)
}
Instruction::LoopStart { params_index } => {
if let Some(params) = instruction_data.get_loop_params(*params_index) {
let mode_str = match params.mode {
LoopMode::Any => "any",
LoopMode::Every => "every",
LoopMode::ForEach => "foreach",
};
let base = format!(
"{}LoopStart {} r{},r{} in r{} → r{} {{",
indent,
mode_str,
params.key_reg,
params.value_reg,
params.collection,
params.result_reg
);
let comment = format!(
"{} loop over r{}, body: {}-{} (P{})",
mode_str, params.collection, params.body_start, params.loop_end, params_index
);
align_comment(&base, &comment, config.comment_column)
} else {
let base = format!("{}LoopStart [INVALID P({})] {{", indent, params_index);
align_comment(
&base,
"ERROR: Invalid loop parameters",
config.comment_column,
)
}
}
Instruction::LoopNext {
body_start,
loop_end,
} => {
let base = format!(
"{}}} continue → {} or exit → {}",
indent, body_start, loop_end
);
let comment = format!(
"Next iteration or exit loop (body:{}-{})",
body_start, loop_end
);
align_comment(&base, &comment, config.comment_column)
}
Instruction::CallRule { dest, rule_index } => {
let rule_name = program
.rule_infos
.get(*rule_index as usize)
.map(|info| info.name.as_str())
.unwrap_or("<invalid>");
let base = format!("{}CallRule r{}{}", indent, dest, rule_name);
let comment = format!("Call rule '{}' (R{}) with caching", rule_name, rule_index);
align_comment(&base, &comment, config.comment_column)
}
Instruction::RuleInit {
result_reg,
rule_index,
} => {
let rule_name = program
.rule_infos
.get(*rule_index as usize)
.map(|info| info.name.as_str())
.unwrap_or("<invalid>");
let base = format!("{}RuleInit {} → r{} {{", indent, rule_name, result_reg);
let comment = format!(
"Initialize rule '{}' (R{}) evaluation",
rule_name, rule_index
);
align_comment(&base, &comment, config.comment_column)
}
Instruction::RuleReturn {} => {
let base = format!("{}}} return from rule", indent);
align_comment(&base, "End of rule evaluation", config.comment_column)
}
Instruction::ChainedIndex { params_index } => {
let (base, comment) =
if let Some(params) = instruction_data.get_chained_index_params(*params_index) {
let chain_parts: Vec<String> = params
.path_components
.iter()
.map(|component| match component {
crate::rvm::instructions::LiteralOrRegister::Literal(idx) => {
if let Some(literal) = program.literals.get(*idx as usize) {
match literal {
crate::Value::String(s) => format!(".{}", s.as_ref()),
other => format!(
"[{}]",
serde_json::to_string(other)
.unwrap_or_else(|_| "?".to_string())
),
}
} else {
format!("[L{}?]", idx)
}
}
crate::rvm::instructions::LiteralOrRegister::Register(reg) => {
format!("[r{}]", reg)
}
})
.collect();
let chain_display = if chain_parts.is_empty() {
String::new()
} else {
format!(" r{}{}", params.root, chain_parts.join(""))
};
let base_str = format!(
"{}ChainedIndex r{} ← r{}{}",
indent, params.dest, params.root, chain_display
);
let comment_str = format!(
"Multi-level chained indexing: r{} → r{}",
params.root, params.dest
);
(base_str, comment_str)
} else {
let base_str = format!("{}ChainedIndex chained_index", indent);
let comment_str = "Multi-level chained indexing (invalid params)".to_string();
(base_str, comment_str)
};
align_comment(&base, &comment, config.comment_column)
}
Instruction::VirtualDataDocumentLookup { .. } => {
let base = format!(
"{}VirtualDataDocumentLookup virtual_data_document_lookup",
indent
);
align_comment(
&base,
"Lookup in data namespace virtual documents",
config.comment_column,
)
}
Instruction::DestructuringSuccess {} => {
let base = format!("{}DestructuringSuccess ✓", indent);
align_comment(
&base,
"Parameter destructuring validated",
config.comment_column,
)
}
Instruction::Halt {} => {
let base = format!("{}Halt halt", indent);
align_comment(&base, "Stop execution", config.comment_column)
}
Instruction::ComprehensionBegin { params_index } => {
if let Some(params) = instruction_data.get_comprehension_begin_params(*params_index) {
let mode_str = match params.mode {
crate::rvm::instructions::ComprehensionMode::Array => "array",
crate::rvm::instructions::ComprehensionMode::Set => "set",
crate::rvm::instructions::ComprehensionMode::Object => "object",
};
let (source_desc, result_desc) = if params.collection_reg == params.result_reg {
(
format!("r{}", params.collection_reg),
format!("r{}", params.result_reg),
)
} else {
(
format!("r{} (src)", params.collection_reg),
format!("r{} (dst)", params.result_reg),
)
};
let base = format!(
"{}CompBegin {} {}{} k:{} v:{} {{",
indent, mode_str, source_desc, result_desc, params.key_reg, params.value_reg
);
let comment = format!(
"{} comprehension in r{}, body: {}-{} (P{})",
mode_str,
params.collection_reg,
params.body_start,
params.comprehension_end,
params_index
);
align_comment(&base, &comment, config.comment_column)
} else {
let base = format!("{}CompBegin [INVALID P({})] {{", indent, params_index);
align_comment(
&base,
"ERROR: Invalid comprehension parameters",
config.comment_column,
)
}
}
Instruction::ComprehensionYield { value_reg, key_reg } => {
let base = match key_reg {
Some(k) => format!("{}CompYield r{} r{}", indent, k, value_reg),
None => format!("{}CompYield r{}", indent, value_reg),
};
align_comment(&base, "Yield value to comprehension", config.comment_column)
}
Instruction::ComprehensionEnd {} => {
let base = format!("{}}} CompEnd", indent);
align_comment(&base, "End comprehension block", config.comment_column)
}
}
}
/// Generate compact tabular assembly listing
pub fn generate_tabular_assembly_listing(
program: &Program,
_config: &AssemblyListingConfig,
) -> String {
let mut output = String::new();
let mut indent_level: usize = 0;
// Add header
writeln!(output, "; RVM Assembly (Tabular Format)").unwrap();
writeln!(
output,
"; {} instructions, {} literals",
program.instructions.len(),
program.literals.len()
)
.unwrap();
writeln!(output, ";").unwrap();
writeln!(output, "; PC | Instruction | Operation").unwrap();
writeln!(output, ";-----|--------------|----------").unwrap();
for (pc, instruction) in program.instructions.iter().enumerate() {
// Handle loop indentation
match instruction {
Instruction::LoopNext { .. } => {
indent_level = indent_level.saturating_sub(1);
}
Instruction::RuleReturn { .. } => {
indent_level = indent_level.saturating_sub(1);
}
_ => {}
}
let indent = " ".repeat(indent_level * 2); // Smaller indent for tabular format
// Format in tabular style
let addr_str = format!("{:03}", pc);
let inst_name = get_instruction_name(instruction);
let operation =
format_operation_compact(instruction, &indent, &program.instruction_data, program);
writeln!(output, "{:>4} | {:12} | {}", addr_str, inst_name, operation).unwrap();
// Increase indentation for loop/rule starts
match instruction {
Instruction::LoopStart { .. } => {
indent_level += 1;
}
Instruction::RuleInit { .. } => {
indent_level += 1;
}
_ => {}
}
}
output
}
fn get_instruction_name(instruction: &Instruction) -> &'static str {
match instruction {
Instruction::Load { .. } => "LOAD",
Instruction::LoadTrue { .. } => "LOAD_TRUE",
Instruction::LoadFalse { .. } => "LOAD_FALSE",
Instruction::LoadNull { .. } => "LOAD_NULL",
Instruction::LoadBool { .. } => "LOAD_BOOL",
Instruction::LoadData { .. } => "LOAD_DATA",
Instruction::LoadInput { .. } => "LOAD_INPUT",
Instruction::Move { .. } => "MOVE",
Instruction::Add { .. } => "ADD",
Instruction::Sub { .. } => "SUB",
Instruction::Mul { .. } => "MUL",
Instruction::Div { .. } => "DIV",
Instruction::Mod { .. } => "MOD",
Instruction::Eq { .. } => "EQ",
Instruction::Ne { .. } => "NE",
Instruction::Lt { .. } => "LT",
Instruction::Le { .. } => "LE",
Instruction::Gt { .. } => "GT",
Instruction::Ge { .. } => "GE",
Instruction::And { .. } => "AND",
Instruction::Or { .. } => "OR",
Instruction::Not { .. } => "NOT",
Instruction::BuiltinCall { .. } => "BUILTIN_CALL",
Instruction::FunctionCall { .. } => "FUNC_CALL",
Instruction::HostAwait { .. } => "HOST_AWAIT",
Instruction::Return { .. } => "RETURN",
Instruction::ObjectSet { .. } => "OBJ_SET",
Instruction::ObjectCreate { .. } => "OBJ_CREATE",
Instruction::Index { .. } => "INDEX",
Instruction::IndexLiteral { .. } => "INDEX_LIT",
Instruction::ArrayNew { .. } => "ARRAY_NEW",
Instruction::ArrayPush { .. } => "ARRAY_PUSH",
Instruction::ArrayCreate { .. } => "ARRAY_CREATE",
Instruction::SetNew { .. } => "SET_NEW",
Instruction::SetAdd { .. } => "SET_ADD",
Instruction::SetCreate { .. } => "SET_CREATE",
Instruction::Contains { .. } => "CONTAINS",
Instruction::Count { .. } => "COUNT",
Instruction::AssertCondition { .. } => "ASSERT",
Instruction::AssertNotUndefined { .. } => "ASSERT_NOT_UNDEF",
Instruction::LoopStart { .. } => "LOOP_START",
Instruction::LoopNext { .. } => "LOOP_NEXT",
Instruction::CallRule { .. } => "CALL_RULE",
Instruction::RuleInit { .. } => "RULE_INIT",
Instruction::RuleReturn { .. } => "RULE_RET",
Instruction::DestructuringSuccess {} => "DESTRUCT_SUCCESS",
Instruction::ChainedIndex { .. } => "CHAINED_INDEX",
Instruction::VirtualDataDocumentLookup { .. } => "VIRTUAL_DATA_DOC_LOOKUP",
Instruction::Halt {} => "HALT",
Instruction::ComprehensionBegin { .. } => "COMP_BEGIN",
Instruction::ComprehensionYield { .. } => "COMP_YIELD",
Instruction::ComprehensionEnd {} => "COMP_END",
}
}
fn format_operation_compact(
instruction: &Instruction,
indent: &str,
instruction_data: &InstructionData,
_program: &Program,
) -> String {
match instruction {
Instruction::Load { dest, literal_idx } => {
format!("{}r{} ← L{}", indent, dest, literal_idx)
}
Instruction::LoadInput { dest } => {
format!("{}r{} ← input", indent, dest)
}
Instruction::LoadData { dest } => {
format!("{}r{} ← data", indent, dest)
}
Instruction::Move { dest, src } => {
format!("{}r{} ← r{}", indent, dest, src)
}
Instruction::Add { dest, left, right } => {
format!("{}r{} ← r{} + r{}", indent, dest, left, right)
}
Instruction::Index {
dest,
container,
key,
} => {
format!("{}r{} ← r{}[r{}]", indent, dest, container, key)
}
Instruction::IndexLiteral {
dest,
container,
literal_idx,
} => {
format!("{}r{} ← r{}[L{}]", indent, dest, container, literal_idx)
}
Instruction::LoopStart { params_index } => {
if let Some(params) = instruction_data.get_loop_params(*params_index) {
format!(
"{}loop r{} in r{} {{",
indent, params.value_reg, params.collection
)
} else {
format!("{}loop P({}) {{", indent, params_index)
}
}
Instruction::LoopNext { .. } => {
format!("{}}}", indent)
}
Instruction::CallRule { dest, rule_index } => {
format!("{}r{} ← rule_{}", indent, dest, rule_index)
}
Instruction::HostAwait { dest, arg, id } => {
format!("{}await r{} → r{} (id r{})", indent, arg, dest, id)
}
Instruction::RuleInit {
result_reg,
rule_index,
} => {
format!("{}rule_{} → r{} {{", indent, rule_index, result_reg)
}
Instruction::RuleReturn {} => {
format!("{}}}", indent)
}
Instruction::DestructuringSuccess {} => {
format!("{}✓ destructuring validated", indent)
}
_ => {
// For other instructions, use a simplified version
format!(
"{}{}",
indent,
instruction
.to_string()
.replace("R(", "r")
.replace(")", "")
.replace("L(", "L")
)
}
}
}

19
src/rvm/program/mod.rs Normal file
View File

@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
mod core;
mod listing;
mod recompile;
mod rule_tree;
mod serialization;
mod types;
pub use core::Program;
pub use listing::{
generate_assembly_listing, generate_tabular_assembly_listing, AssemblyListingConfig,
};
pub(crate) use serialization::value::{binaries_to_values, BinaryValue};
pub use serialization::{DeserializationResult, VersionedProgram};
pub use types::{
BuiltinInfo, FunctionInfo, ProgramMetadata, RuleInfo, RuleType, SourceFile, SpanInfo,
};

View File

@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use super::Program;
use alloc::string::{String, ToString};
impl Program {
/// Compile a partial deserialized program to a complete one
///
/// This method takes a partial program (containing only entry_points and sources)
/// and recompiles it to create a complete program with all instructions and data.
pub fn compile_from_partial(partial_program: Program) -> Result<Program, String> {
if partial_program.entry_points.is_empty() {
return Err("Partial program must contain entry points".to_string());
}
if partial_program.sources.is_empty() {
return Err("Partial program must contain sources".to_string());
}
Err("Recompilation from partial program is not yet implemented".to_string())
}
}

View File

@@ -0,0 +1,105 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use anyhow::Result as AnyResult;
use crate::value::Value;
use super::Program;
impl Program {
/// Add a rule to the rule tree
/// path: Package path components (e.g., ["p1", "p2"] for data.p1.p2.rule)
/// rule_name: Rule name (e.g., "rule")
/// rule_index: Index of the rule in rule_infos
pub fn add_rule_to_tree(
&mut self,
path: &[String],
rule_name: &str,
rule_index: usize,
) -> AnyResult<()> {
let mut full_path = Vec::with_capacity(path.len() + 1);
full_path.extend(path.iter().map(|s| s.as_str()));
full_path.push(rule_name);
let target = self.rule_tree.make_or_get_value_mut(&full_path)?;
*target = Value::Number(rule_index.into());
Ok(())
}
/// Check for conflicts between rule tree and data
/// Returns an error if any rule path conflicts with data paths
pub fn check_rule_data_conflicts(&self, data: &Value) -> Result<(), crate::rvm::vm::VmError> {
let actual_rule_tree = &self.rule_tree["data"];
match actual_rule_tree {
Value::Undefined => return Ok(()),
Value::Object(rule_obj) if rule_obj.is_empty() => return Ok(()),
_ => {}
}
Self::check_conflicts_recursive(actual_rule_tree, data, &mut Vec::new())
}
fn check_conflicts_recursive(
rule_tree: &Value,
data: &Value,
current_path: &mut Vec<String>,
) -> Result<(), crate::rvm::vm::VmError> {
match rule_tree {
Value::Object(rule_obj) => {
for (key, rule_value) in rule_obj.iter() {
if let Value::String(key_str) = key {
current_path.push(key_str.to_string());
let data_value = &data[key];
match rule_value {
Value::Number(_) => {
if data_value != &Value::Undefined {
return Err(crate::rvm::vm::VmError::RuleDataConflict(format!(
"Conflict: rule defines path '{}' but data also provides this path",
current_path.join("."),
)));
}
}
Value::Object(_) => {
if let Value::Object(_) = data_value {
Self::check_conflicts_recursive(
rule_value,
data_value,
current_path,
)?;
} else if data_value != &Value::Undefined {
return Err(crate::rvm::vm::VmError::RuleDataConflict(format!(
"Conflict: rule defines subpaths under '{}' but data provides a non-object value at this path",
current_path.join("."),
)));
}
}
_ => {
return Err(crate::rvm::vm::VmError::RuleDataConflict(format!(
"Invalid rule tree structure at path '{}'",
current_path.join("."),
)));
}
}
current_path.pop();
}
}
}
_ => {
return Err(crate::rvm::vm::VmError::RuleDataConflict(
"Rule tree root must be an object".to_string(),
));
}
}
Ok(())
}
}

View File

@@ -0,0 +1,482 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use bincode::config::standard;
use bincode::serde::{decode_from_slice, encode_to_vec};
use indexmap::IndexMap;
use super::super::types::SourceFile;
use super::{DeserializationResult, Program};
use crate::value::Value;
use super::value::{
binaries_to_values, binary_to_value, BinaryValue, BinaryValueRef, BinaryValueSlice,
};
type ArtifactData = (IndexMap<String, usize>, Vec<SourceFile>, bool);
impl Program {
/// Serialize program to binary format.
/// Uses pure bincode for all sections now that `Value` supports serde.
pub fn serialize_binary(&self) -> Result<Vec<u8>, String> {
let mut buffer = Vec::new();
buffer.extend_from_slice(&Self::MAGIC);
buffer.extend_from_slice(&Self::SERIALIZATION_VERSION.to_le_bytes());
let entry_points_bin = encode_to_vec(&self.entry_points, standard())
.map_err(|e| format!("Entry points bincode serialization failed: {}", e))?;
let sources_bin = encode_to_vec(&self.sources, standard())
.map_err(|e| format!("Sources bincode serialization failed: {}", e))?;
let literals_bin = encode_to_vec(BinaryValueSlice(self.literals.as_slice()), standard())
.map_err(|e| format!("Literals bincode serialization failed: {}", e))?;
let rule_tree_bin = encode_to_vec(BinaryValueRef(&self.rule_tree), standard())
.map_err(|e| format!("Rule tree bincode serialization failed: {}", e))?;
let binary_data = encode_to_vec(self, standard())
.map_err(|e| format!("Program structure binary serialization failed: {}", e))?;
buffer.extend_from_slice(&(entry_points_bin.len() as u32).to_le_bytes());
buffer.extend_from_slice(&(sources_bin.len() as u32).to_le_bytes());
buffer.extend_from_slice(&(literals_bin.len() as u32).to_le_bytes());
buffer.extend_from_slice(&(rule_tree_bin.len() as u32).to_le_bytes());
buffer.push(if self.rego_v0 { 1 } else { 0 });
buffer.extend_from_slice(&entry_points_bin);
buffer.extend_from_slice(&sources_bin);
buffer.extend_from_slice(&literals_bin);
buffer.extend_from_slice(&rule_tree_bin);
buffer.extend_from_slice(&(binary_data.len() as u32).to_le_bytes());
buffer.extend_from_slice(&binary_data);
Ok(buffer)
}
/// Deserialize only the artifact section (entry_points and sources) from binary format
pub fn deserialize_artifacts_only(data: &[u8]) -> Result<ArtifactData, String> {
if data.len() < 9 {
return Err("Data too short for artifact header".to_string());
}
if data[0..4] != Self::MAGIC {
return Err("Invalid file format - magic number mismatch".to_string());
}
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
match version {
1 => {
if data.len() < 17 {
return Err("Data too short for artifact header".to_string());
}
let entry_points_len =
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let sources_len =
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
let rego_v0 = data[16] != 0;
let entry_points_start = 17;
let sources_start = entry_points_start + entry_points_len;
let sources_end = sources_start + sources_len;
if data.len() < sources_end {
return Err("Data truncated in artifact section".to_string());
}
let entry_points =
decode_from_slice(&data[entry_points_start..sources_start], standard())
.map(|(value, _)| value)
.unwrap_or_else(|_| IndexMap::new());
let sources = decode_from_slice(&data[sources_start..sources_end], standard())
.map(|(value, _)| value)
.unwrap_or_else(|_| Vec::new());
Ok((entry_points, sources, rego_v0))
}
2 | 3 => {
if data.len() < 25 {
return Err("Data too short for artifact header".to_string());
}
let entry_points_len =
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let sources_len =
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
let literals_len =
u32::from_le_bytes([data[16], data[17], data[18], data[19]]) as usize;
let rule_tree_len =
u32::from_le_bytes([data[20], data[21], data[22], data[23]]) as usize;
let rego_v0 = data[24] != 0;
let entry_points_start = 25;
let sources_start = entry_points_start + entry_points_len;
let literals_start = sources_start + sources_len;
let rule_tree_start = literals_start + literals_len;
let rule_tree_end = rule_tree_start + rule_tree_len;
if data.len() < rule_tree_end {
return Err("Data truncated in artifact section".to_string());
}
let entry_points =
decode_from_slice(&data[entry_points_start..sources_start], standard())
.map(|(value, _)| value)
.unwrap_or_else(|_| IndexMap::new());
let sources = decode_from_slice(&data[sources_start..literals_start], standard())
.map(|(value, _)| value)
.unwrap_or_else(|_| Vec::new());
Ok((entry_points, sources, rego_v0))
}
v => Err(format!("Unsupported version {}", v)),
}
}
/// Deserialize program from binary format with version checking
pub fn deserialize_binary(data: &[u8]) -> Result<DeserializationResult, String> {
if data.len() < 9 {
return Err("Data too short for header".to_string());
}
if data[0..4] != Self::MAGIC {
return Err("Invalid file format - magic number mismatch".to_string());
}
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
if version > Self::SERIALIZATION_VERSION {
return Err(format!(
"Unsupported version {}. Maximum supported version is {}",
version,
Self::SERIALIZATION_VERSION
));
}
match version {
1 => {
if data.len() < 25 {
return Err("Data too short for header".to_string());
}
let entry_points_len =
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let sources_len =
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
let rego_v0 = data[16] != 0;
let entry_points_start = 17;
let sources_start = entry_points_start + entry_points_len;
let binary_len_start = sources_start + sources_len;
if data.len() < binary_len_start + 4 {
return Err("Data too short for binary length".to_string());
}
let binary_len = u32::from_le_bytes([
data[binary_len_start],
data[binary_len_start + 1],
data[binary_len_start + 2],
data[binary_len_start + 3],
]) as usize;
let json_len_start = binary_len_start + 4 + binary_len;
if data.len() < json_len_start + 4 {
return Err("Data too short for JSON length".to_string());
}
let json_len = u32::from_le_bytes([
data[json_len_start],
data[json_len_start + 1],
data[json_len_start + 2],
data[json_len_start + 3],
]) as usize;
let total_expected = json_len_start + 4 + json_len;
if data.len() < total_expected {
return Err("Data truncated".to_string());
}
let binary_start = binary_len_start + 4;
let json_start = json_len_start + 4;
let entry_points =
decode_from_slice(&data[entry_points_start..sources_start], standard())
.map(|(value, _)| value)
.map_err(|e| format!("Entry points deserialization failed: {}", e))?;
let sources = decode_from_slice(&data[sources_start..binary_len_start], standard())
.map(|(value, _)| value)
.map_err(|e| format!("Sources deserialization failed: {}", e))?;
let mut needs_recompilation = false;
let mut program = match decode_from_slice::<Program, _>(
&data[binary_start..json_start],
standard(),
) {
Ok((prog, _)) => prog,
Err(_e) => {
needs_recompilation = true;
Program::new()
}
};
let (literals, rule_tree) = match serde_json::from_slice::<serde_json::Value>(
&data[json_start..json_start + json_len],
) {
Ok(combined) => {
let literals = combined
.get("literals")
.and_then(|v| serde_json::from_value::<Vec<Value>>(v.clone()).ok())
.unwrap_or_else(|| {
needs_recompilation = true;
Vec::new()
});
let rule_tree = combined
.get("rule_tree")
.and_then(|v| serde_json::from_value::<Value>(v.clone()).ok())
.unwrap_or_else(|| {
needs_recompilation = true;
Value::new_object()
});
(literals, rule_tree)
}
Err(_e) => {
needs_recompilation = true;
(Vec::new(), Value::new_object())
}
};
program.entry_points = entry_points;
program.sources = sources;
program.literals = literals;
program.rule_tree = rule_tree;
program.rego_v0 = rego_v0;
program.needs_recompilation = needs_recompilation;
if !program.builtin_info_table.is_empty() {
if let Err(_e) = program.initialize_resolved_builtins() {
program.needs_recompilation = true;
}
}
if program.needs_recompilation {
Ok(DeserializationResult::Partial(program))
} else {
Ok(DeserializationResult::Complete(program))
}
}
2 | 3 => {
if data.len() < 29 {
return Err("Data too short for header".to_string());
}
let entry_points_len =
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let sources_len =
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
let literals_len =
u32::from_le_bytes([data[16], data[17], data[18], data[19]]) as usize;
let rule_tree_len =
u32::from_le_bytes([data[20], data[21], data[22], data[23]]) as usize;
let rego_v0 = data[24] != 0;
let entry_points_start = 25;
let sources_start = entry_points_start + entry_points_len;
let literals_start = sources_start + sources_len;
let rule_tree_start = literals_start + literals_len;
let binary_len_start = rule_tree_start + rule_tree_len;
if data.len() < binary_len_start + 4 {
return Err("Data too short for binary length".to_string());
}
let binary_len = u32::from_le_bytes([
data[binary_len_start],
data[binary_len_start + 1],
data[binary_len_start + 2],
data[binary_len_start + 3],
]) as usize;
let binary_start = binary_len_start + 4;
let binary_end = binary_start + binary_len;
if data.len() < binary_end {
return Err("Data truncated".to_string());
}
let entry_points =
decode_from_slice(&data[entry_points_start..sources_start], standard())
.map(|(value, _)| value)
.map_err(|e| format!("Entry points deserialization failed: {}", e))?;
let sources = decode_from_slice(&data[sources_start..literals_start], standard())
.map(|(value, _)| value)
.map_err(|e| format!("Sources deserialization failed: {}", e))?;
let mut needs_recompilation = false;
let literals = match decode_from_slice::<Vec<BinaryValue>, _>(
&data[literals_start..rule_tree_start],
standard(),
) {
Ok((binary_literals, _)) => match binaries_to_values(binary_literals) {
Ok(values) => values,
Err(_e) => {
needs_recompilation = true;
Vec::new()
}
},
Err(_e) => {
needs_recompilation = true;
Vec::new()
}
};
let rule_tree = match decode_from_slice::<BinaryValue, _>(
&data[rule_tree_start..binary_len_start],
standard(),
) {
Ok((binary_tree, _)) => match binary_to_value(binary_tree) {
Ok(value) => value,
Err(_e) => {
needs_recompilation = true;
Value::new_object()
}
},
Err(_e) => {
needs_recompilation = true;
Value::new_object()
}
};
let mut program = match decode_from_slice::<Program, _>(
&data[binary_start..binary_end],
standard(),
) {
Ok((prog, _)) => prog,
Err(_e) => {
needs_recompilation = true;
Program::new()
}
};
program.entry_points = entry_points;
program.sources = sources;
program.literals = literals;
program.rule_tree = rule_tree;
program.rego_v0 = rego_v0;
program.needs_recompilation = needs_recompilation;
if !program.builtin_info_table.is_empty() {
if let Err(_e) = program.initialize_resolved_builtins() {
program.needs_recompilation = true;
}
}
if program.needs_recompilation {
Ok(DeserializationResult::Partial(program))
} else {
Ok(DeserializationResult::Complete(program))
}
}
v => Err(format!("Unsupported version {}", v)),
}
}
/// Check if data can be deserialized without actually deserializing
pub fn can_deserialize(data: &[u8]) -> Result<bool, String> {
if data.len() < 8 {
return Ok(false);
}
if data[0..4] != Self::MAGIC {
return Ok(false);
}
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
match version {
1..=3 => Ok(true),
_ => Ok(false),
}
}
/// Get file format information without deserializing
pub fn get_file_info(data: &[u8]) -> Result<(u32, usize), String> {
if data.len() < 9 {
return Err("Data too short for header".to_string());
}
if data[0..4] != Self::MAGIC {
return Err("Invalid file format".to_string());
}
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
match version {
1 => {
if data.len() < 25 {
return Err("Data too short for header".to_string());
}
let entry_points_len =
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let sources_len =
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
let binary_len_start = 17 + entry_points_len + sources_len;
if data.len() < binary_len_start + 4 {
return Err("Data too short for binary length".to_string());
}
let binary_len = u32::from_le_bytes([
data[binary_len_start],
data[binary_len_start + 1],
data[binary_len_start + 2],
data[binary_len_start + 3],
]) as usize;
Ok((version, binary_len))
}
2 | 3 => {
if data.len() < 29 {
return Err("Data too short for header".to_string());
}
let entry_points_len =
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let sources_len =
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
let literals_len =
u32::from_le_bytes([data[16], data[17], data[18], data[19]]) as usize;
let rule_tree_len =
u32::from_le_bytes([data[20], data[21], data[22], data[23]]) as usize;
let binary_len_start =
25 + entry_points_len + sources_len + literals_len + rule_tree_len;
if data.len() < binary_len_start + 4 {
return Err("Data too short for binary length".to_string());
}
let binary_len = u32::from_le_bytes([
data[binary_len_start],
data[binary_len_start + 1],
data[binary_len_start + 2],
data[binary_len_start + 3],
]) as usize;
Ok((version, binary_len))
}
v => Err(format!("Unsupported version {}", v)),
}
}
}

View File

@@ -0,0 +1,196 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use super::super::types::SourceFile;
use super::super::types::{BuiltinInfo, ProgramMetadata, RuleInfo, SpanInfo};
use super::Program;
use crate::rvm::instructions::InstructionData;
use crate::rvm::Instruction;
use crate::value::Value;
use indexmap::IndexMap;
impl Program {
/// Serialize to JSON format with complete program information and proper field names
pub fn serialize_json(&self) -> Result<String, String> {
let json_data = serde_json::json!({
"metadata": {
"compiler_version": self.metadata.compiler_version,
"compiled_at": self.metadata.compiled_at,
"source_info": self.metadata.source_info,
"optimization_level": self.metadata.optimization_level,
"rego_v0": self.rego_v0,
"needs_runtime_recursion_check": self.needs_runtime_recursion_check,
"needs_recompilation": self.needs_recompilation
},
"program_structure": {
"main_entry_point": self.main_entry_point,
"max_rule_window_size": self.max_rule_window_size,
"dispatch_window_size": self.dispatch_window_size,
},
"instructions": self.instructions,
"instruction_data": {
"loop_params": self.instruction_data.loop_params,
"builtin_call_params": self.instruction_data.builtin_call_params,
"function_call_params": self.instruction_data.function_call_params,
"object_create_params": self.instruction_data.object_create_params,
"array_create_params": self.instruction_data.array_create_params,
"set_create_params": self.instruction_data.set_create_params,
"virtual_data_document_lookup_params": self.instruction_data.virtual_data_document_lookup_params,
"chained_index_params": self.instruction_data.chained_index_params,
"comprehension_begin_params": self.instruction_data.comprehension_begin_params
},
"literals": self.literals,
"builtin_info_table": self.builtin_info_table,
"entry_points": self.entry_points,
"sources": self.sources,
"rule_infos": self.rule_infos,
"instruction_spans": self.instruction_spans,
"rule_tree": self.rule_tree
});
serde_json::to_string_pretty(&json_data)
.map_err(|e| format!("JSON serialization failed: {}", e))
}
/// Deserialize program from JSON format
pub fn deserialize_json(data: &str) -> Result<Program, String> {
let json_data: serde_json::Value =
serde_json::from_str(data).map_err(|e| format!("JSON parsing failed: {}", e))?;
let metadata = json_data
.get("metadata")
.ok_or("Missing metadata section")?;
let compiler_version = metadata
.get("compiler_version")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string();
let compiled_at = metadata
.get("compiled_at")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string();
let source_info = metadata
.get("source_info")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let optimization_level = metadata
.get("optimization_level")
.and_then(|v| v.as_u64())
.unwrap_or(0) as u8;
let rego_v0 = metadata
.get("rego_v0")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let needs_runtime_recursion_check = metadata
.get("needs_runtime_recursion_check")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let needs_recompilation = metadata
.get("needs_recompilation")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let program_structure = json_data
.get("program_structure")
.ok_or("Missing program_structure section")?;
let main_entry_point = program_structure
.get("main_entry_point")
.and_then(|v| v.as_u64())
.unwrap_or(0) as usize;
let max_rule_window_size = program_structure
.get("max_rule_window_size")
.and_then(|v| v.as_u64())
.unwrap_or(0) as usize;
let dispatch_window_size = program_structure
.get("dispatch_window_size")
.and_then(|v| v.as_u64())
.unwrap_or(0) as usize;
let instructions: Vec<Instruction> = serde_json::from_value(
json_data
.get("instructions")
.ok_or("Missing instructions section")?
.clone(),
)
.map_err(|e| format!("Failed to deserialize instructions: {}", e))?;
let instruction_data_json = json_data
.get("instruction_data")
.ok_or("Missing instruction_data section")?;
let instruction_data: InstructionData =
serde_json::from_value(instruction_data_json.clone())
.map_err(|e| format!("Failed to deserialize instruction_data: {}", e))?;
let literals: Vec<Value> = json_data
.get("literals")
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
.unwrap_or_default();
let builtin_info_table: Vec<BuiltinInfo> = json_data
.get("builtin_info_table")
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
.unwrap_or_default();
let entry_points: IndexMap<String, usize> = json_data
.get("entry_points")
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
.unwrap_or_default();
let sources: Vec<SourceFile> = json_data
.get("sources")
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
.unwrap_or_default();
let rule_infos: Vec<RuleInfo> = json_data
.get("rule_infos")
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
.unwrap_or_default();
let instruction_spans: Vec<Option<SpanInfo>> = json_data
.get("instruction_spans")
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
.unwrap_or_default();
let rule_tree: Value = json_data
.get("rule_tree")
.map(|v| serde_json::from_value(v.clone()).unwrap_or_else(|_| Value::new_object()))
.unwrap_or_else(Value::new_object);
let mut program = Program {
instructions,
literals,
instruction_data,
builtin_info_table,
entry_points,
sources,
rule_infos,
instruction_spans,
main_entry_point,
max_rule_window_size,
dispatch_window_size,
metadata: ProgramMetadata {
compiler_version,
compiled_at,
source_info,
optimization_level,
},
rule_tree,
resolved_builtins: Vec::new(),
needs_runtime_recursion_check,
needs_recompilation,
rego_v0,
};
if !program.builtin_info_table.is_empty() {
let _ = program.initialize_resolved_builtins();
}
Ok(program)
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
pub(crate) mod binary;
mod json;
pub(crate) mod value;
use serde::{Deserialize, Serialize};
use super::Program;
/// Versioned program wrapper for serialization compatibility
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionedProgram {
/// Format version for compatibility checking
pub version: u32,
/// The actual program data
pub program: Program,
}
/// Result of program deserialization that explicitly indicates completeness
#[derive(Debug, Clone)]
pub enum DeserializationResult {
/// Full deserialization was successful - program is fully functional
Complete(Program),
/// Only artifact section was deserialized - extensible sections failed
/// The program contains entry_points and sources but requires recompilation
Partial(Program),
}

View File

@@ -0,0 +1,294 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use core::str::FromStr;
use serde::de::{self, EnumAccess, VariantAccess, Visitor};
use serde::ser::{SerializeSeq, SerializeTuple};
use serde::{Deserialize, Serialize};
use crate::number::Number;
use crate::value::Value;
const VARIANT_NULL: u32 = 0;
const VARIANT_BOOL: u32 = 1;
const VARIANT_NUMBER_STRING: u32 = 2;
const VARIANT_STRING: u32 = 3;
const VARIANT_ARRAY: u32 = 4;
const VARIANT_SET: u32 = 5;
const VARIANT_OBJECT: u32 = 6;
const VARIANT_UNDEFINED: u32 = 7;
const VARIANT_NUMBER_I64: u32 = 8;
const VARIANT_NUMBER_U64: u32 = 9;
const VARIANT_NUMBER_F64: u32 = 10;
/// Wrapper type for zero-copy binary serialization of a `Value`.
/// Keeps references into the original data so collections and strings are not cloned.
pub(crate) struct BinaryValueRef<'a>(pub &'a Value);
impl<'a> Serialize for BinaryValueRef<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self.0 {
Value::Null => serializer.serialize_unit_variant("BinaryValue", VARIANT_NULL, "Null"),
Value::Bool(b) => {
serializer.serialize_newtype_variant("BinaryValue", VARIANT_BOOL, "Bool", &b)
}
Value::Number(n) => {
if let Some(value) = n.as_i64() {
serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_NUMBER_I64,
"NumberI64",
&value,
)
} else if let Some(value) = n.as_u64() {
serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_NUMBER_U64,
"NumberU64",
&value,
)
} else if let Some(value) = n.as_f64() {
serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_NUMBER_F64,
"NumberF64",
&value,
)
} else {
serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_NUMBER_STRING,
"Number",
&n.format_scientific(),
)
}
}
Value::String(s) => serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_STRING,
"String",
s.as_ref(),
),
Value::Array(items) => serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_ARRAY,
"Array",
&BinaryValueSlice(items.as_slice()),
),
Value::Set(items) => serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_SET,
"Set",
&BinarySetRef(items.as_ref()),
),
Value::Object(entries) => serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_OBJECT,
"Object",
&BinaryObjectRef(entries.as_ref()),
),
Value::Undefined => {
serializer.serialize_unit_variant("BinaryValue", VARIANT_UNDEFINED, "Undefined")
}
}
}
}
/// Slice wrapper allowing zero-copy serialization of value collections.
pub(crate) struct BinaryValueSlice<'a>(pub &'a [Value]);
impl<'a> Serialize for BinaryValueSlice<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
for value in self.0 {
seq.serialize_element(&BinaryValueRef(value))?;
}
seq.end()
}
}
struct BinarySetRef<'a>(&'a BTreeSet<Value>);
impl<'a> Serialize for BinarySetRef<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
for value in self.0.iter() {
seq.serialize_element(&BinaryValueRef(value))?;
}
seq.end()
}
}
struct BinaryObjectRef<'a>(&'a BTreeMap<Value, Value>);
impl<'a> Serialize for BinaryObjectRef<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
for (key, value) in self.0.iter() {
seq.serialize_element(&BinaryEntryRef(key, value))?;
}
seq.end()
}
}
struct BinaryEntryRef<'a>(&'a Value, &'a Value);
impl<'a> Serialize for BinaryEntryRef<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut tuple = serializer.serialize_tuple(2)?;
tuple.serialize_element(&BinaryValueRef(self.0))?;
tuple.serialize_element(&BinaryValueRef(self.1))?;
tuple.end()
}
}
/// Owned counterpart used during deserialization.
#[derive(Debug, Clone)]
pub(crate) struct BinaryValue(pub Value);
impl BinaryValue {
fn into_value(self) -> Value {
self.0
}
}
const BINARY_VARIANTS: &[&str] = &[
"Null",
"Bool",
"Number",
"String",
"Array",
"Set",
"Object",
"Undefined",
"NumberI64",
"NumberU64",
"NumberF64",
];
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
enum BinaryVariant {
Null,
Bool,
Number,
String,
Array,
Set,
Object,
Undefined,
NumberI64,
NumberU64,
NumberF64,
}
struct BinaryValueVisitor;
impl<'de> Visitor<'de> for BinaryValueVisitor {
type Value = BinaryValue;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a BinaryValue enum")
}
fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
where
A: EnumAccess<'de>,
{
match data.variant()? {
(BinaryVariant::Null, variant) => {
variant.unit_variant()?;
Ok(BinaryValue(Value::Null))
}
(BinaryVariant::Bool, variant) => {
let value = variant.newtype_variant::<bool>()?;
Ok(BinaryValue(Value::from(value)))
}
(BinaryVariant::Number, variant) => {
let numeric = variant.newtype_variant::<&'de str>()?;
let number = Number::from_str(numeric).map_err(|_| {
de::Error::custom(format!("Invalid numeric string '{numeric}'"))
})?;
Ok(BinaryValue(Value::from(number)))
}
(BinaryVariant::NumberI64, variant) => {
let value = variant.newtype_variant::<i64>()?;
Ok(BinaryValue(Value::from(value)))
}
(BinaryVariant::NumberU64, variant) => {
let value = variant.newtype_variant::<u64>()?;
Ok(BinaryValue(Value::from(value)))
}
(BinaryVariant::NumberF64, variant) => {
let value = variant.newtype_variant::<f64>()?;
Ok(BinaryValue(Value::from(value)))
}
(BinaryVariant::String, variant) => {
let s = variant.newtype_variant::<&'de str>()?;
Ok(BinaryValue(Value::from(s)))
}
(BinaryVariant::Array, variant) => {
let items: Vec<BinaryValue> = variant.newtype_variant()?;
let values: Vec<Value> = items.into_iter().map(BinaryValue::into_value).collect();
Ok(BinaryValue(Value::from(values)))
}
(BinaryVariant::Set, variant) => {
let items: Vec<BinaryValue> = variant.newtype_variant()?;
let mut set = BTreeSet::new();
for item in items {
set.insert(item.into_value());
}
Ok(BinaryValue(Value::from(set)))
}
(BinaryVariant::Object, variant) => {
let entries: Vec<(BinaryValue, BinaryValue)> = variant.newtype_variant()?;
let mut map = BTreeMap::new();
for (key, value) in entries {
map.insert(key.into_value(), value.into_value());
}
Ok(BinaryValue(Value::from(map)))
}
(BinaryVariant::Undefined, variant) => {
variant.unit_variant()?;
Ok(BinaryValue(Value::Undefined))
}
}
}
}
impl<'de> Deserialize<'de> for BinaryValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_enum("BinaryValue", BINARY_VARIANTS, BinaryValueVisitor)
}
}
pub(crate) fn binaries_to_values(binaries: Vec<BinaryValue>) -> Result<Vec<Value>, String> {
Ok(binaries.into_iter().map(BinaryValue::into_value).collect())
}
pub(crate) fn binary_to_value(binary: BinaryValue) -> Result<Value, String> {
Ok(binary.into_value())
}

182
src/rvm/program/types.rs Normal file
View File

@@ -0,0 +1,182 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::string::String;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
/// Builtin function information stored in program's builtin info table
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuiltinInfo {
/// Builtin function name
pub name: String,
/// Exact number of arguments required
pub num_args: u16,
}
/// Span information for debugging and error reporting
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpanInfo {
/// Index into the source table
pub source_index: usize,
/// Line number (1-based)
pub line: usize,
/// Column number (1-based)
pub column: usize,
/// Length of the span
pub length: usize,
}
impl SpanInfo {
pub fn new(source_index: usize, line: usize, column: usize, length: usize) -> Self {
Self {
source_index,
line,
column,
length,
}
}
/// Create SpanInfo from lexer Span with source table lookup
pub fn from_lexer_span(span: &crate::lexer::Span, source_index: usize) -> Self {
Self {
source_index,
line: span.line as usize,
column: span.col as usize,
length: span.text().len(),
}
}
/// Get source information using the program's source table
pub fn get_source<'a>(&self, source_table: &'a [SourceFile]) -> Option<&'a str> {
source_table
.get(self.source_index)
.map(|s| s.content.as_str())
}
/// Get source name using the program's source table
pub fn get_source_name<'a>(&self, source_table: &'a [SourceFile]) -> Option<&'a str> {
source_table.get(self.source_index).map(|s| s.name.as_str())
}
}
/// Rule type enumeration for different kinds of rules (complete, partial set, partial object)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord)]
pub enum RuleType {
Complete,
PartialSet,
PartialObject,
}
/// Information about function rules
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionInfo {
/// Parameter names in order
pub param_names: Vec<String>,
/// Number of parameters
pub num_params: u32,
}
/// Rule metadata for debugging and introspection
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuleInfo {
/// Rule name (e.g., "data.package.rule_name")
pub name: String,
/// Rule type
pub rule_type: RuleType,
/// Definitions
pub definitions: crate::Rc<Vec<Vec<u32>>>,
/// Function-specific information (only present for function rules)
pub function_info: Option<FunctionInfo>,
/// Index into the program's literal table for default value (only for Complete rules)
pub default_literal_index: Option<u16>,
/// Register allocated for this rule's result accumulation
pub result_reg: u8,
/// Number of registers used by this rule (for register windowing)
pub num_registers: u8,
/// Optional destructuring block entry point per definition
/// Index: definition_index → Some(entry_point) | None
pub destructuring_blocks: Vec<Option<u32>>,
}
impl RuleInfo {
pub fn new(
name: String,
rule_type: RuleType,
definitions: crate::Rc<Vec<Vec<u32>>>,
result_reg: u8,
num_registers: u8,
) -> Self {
let num_definitions = definitions.len();
Self {
name,
rule_type,
definitions,
function_info: None,
default_literal_index: None,
result_reg,
num_registers,
destructuring_blocks: alloc::vec![None; num_definitions],
}
}
/// Create a new function rule with parameter information
pub fn new_function(
name: String,
rule_type: RuleType,
definitions: crate::Rc<Vec<Vec<u32>>>,
param_names: Vec<String>,
result_reg: u8,
num_registers: u8,
) -> Self {
let num_params = param_names.len() as u32;
let num_definitions = definitions.len();
Self {
name,
rule_type,
definitions,
function_info: Some(FunctionInfo {
param_names,
num_params,
}),
default_literal_index: None,
result_reg,
num_registers,
destructuring_blocks: alloc::vec![None; num_definitions],
}
}
/// Set the default literal index for this rule
pub fn set_default_literal_index(&mut self, default_literal_index: u16) {
self.default_literal_index = Some(default_literal_index);
}
}
/// Source file information containing filename and contents
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceFile {
/// Source file identifier/path
pub name: String,
/// The actual source code content
pub content: String,
}
impl SourceFile {
pub fn new(name: String, content: String) -> Self {
Self { name, content }
}
}
/// Program compilation metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProgramMetadata {
/// Compiler version that generated this program
pub compiler_version: String,
/// Compilation timestamp
pub compiled_at: String,
/// Source policy information
pub source_info: String,
/// Optimization level used
pub optimization_level: u8,
}

View File

@@ -0,0 +1,648 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::rvm::instructions::{Instruction, LoopMode};
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use anyhow::{anyhow, bail, Result};
/// Parse a textual instruction like "Load { dest: 0, literal_idx: 1 }"
pub fn parse_instruction(text: &str) -> Result<Instruction> {
let text = text.trim();
// Find the instruction name and parameters
if let Some(brace_start) = text.find('{') {
let name = text[..brace_start].trim();
let params_text = &text[brace_start..];
match name {
"Load" => parse_load(params_text),
"LoadTrue" => parse_load_true(params_text),
"LoadFalse" => parse_load_false(params_text),
"LoadNull" => parse_load_null(params_text),
"LoadBool" => parse_load_bool(params_text),
"LoadData" => parse_load_data(params_text),
"LoadInput" => parse_load_input(params_text),
"Move" => parse_move(params_text),
"Add" => parse_add(params_text),
"Sub" => parse_sub(params_text),
"Mul" => parse_mul(params_text),
"Div" => parse_div(params_text),
"Mod" => parse_mod(params_text),
"Eq" => parse_eq(params_text),
"Ne" => parse_ne_instruction(params_text),
"Lt" => parse_lt(params_text),
"Le" => parse_le_instruction(params_text),
"Gt" => parse_gt(params_text),
"Ge" => parse_ge_instruction(params_text),
"And" => parse_and(params_text),
"Or" => parse_or(params_text),
"Not" => parse_not(params_text),
"Return" => parse_return(params_text),
"RuleInit" => parse_rule_init(params_text),
"RuleReturn" => parse_rule_return(params_text),
"DestructuringSuccess" => parse_destructuring_success(params_text),
"ObjectSet" => parse_object_set(params_text),
"ObjectCreate" => parse_object_create(params_text),
"Index" => parse_index(params_text),
"IndexLiteral" => parse_index_literal(params_text),
"ChainedIndex" => parse_chained_index(params_text),
"ArrayNew" => parse_array_new(params_text),
"ArrayCreate" => parse_array_create(params_text),
"SetCreate" => parse_set_create(params_text),
"ArrayPush" => parse_array_push(params_text),
"SetNew" => parse_set_new(params_text),
"SetAdd" => parse_set_add(params_text),
"Contains" => parse_contains(params_text),
"Count" => parse_count(params_text),
"AssertCondition" => parse_assert_condition(params_text),
"AssertNotUndefined" => parse_assert_not_undefined(params_text),
"BuiltinCall" => parse_builtin_call(params_text),
"FunctionCall" => parse_function_call(params_text),
"CallRule" => parse_call_rule(params_text),
"VirtualDataDocumentLookup" => parse_virtual_data_document_lookup(params_text),
"HostAwait" => parse_host_await(params_text),
"LoopStart" => parse_loop_start(params_text),
"LoopNext" => parse_loop_next(params_text),
"ComprehensionStart" => parse_comprehension_start(params_text),
"ComprehensionAdd" => parse_comprehension_add(params_text),
"ComprehensionBegin" => parse_comprehension_start(params_text),
"ComprehensionYield" => parse_comprehension_add(params_text),
_ => bail!("Unknown instruction: {}", name),
}
} else {
// Handle instructions without parameters (no braces)
let name = text.trim();
match name {
"Halt" => Ok(Instruction::Halt {}),
"RuleReturn" => Ok(Instruction::RuleReturn {}),
"DestructuringSuccess" => Ok(Instruction::DestructuringSuccess {}),
"ComprehensionEnd" => Ok(Instruction::ComprehensionEnd {}),
_ => bail!("Unknown instruction: {}", name),
}
}
}
// Parameter parsing helpers
fn parse_params(text: &str) -> Result<Vec<(String, String)>> {
if !text.starts_with('{') || !text.ends_with('}') {
bail!("Parameters must be enclosed in braces");
}
let inner = &text[1..text.len() - 1];
let mut params = Vec::new();
let mut current = String::new();
let in_value = false;
let mut colon_pos = None;
for ch in inner.chars() {
match ch {
':' if !in_value => {
colon_pos = Some(current.len());
current.push(ch);
}
',' if !in_value => {
if let Some(pos) = colon_pos {
let key = current[..pos].trim().to_string();
let value = current[pos + 1..].trim().to_string();
params.push((key, value));
current.clear();
colon_pos = None;
} else {
bail!("Invalid parameter format");
}
}
_ => current.push(ch),
}
}
// Handle the last parameter
if !current.trim().is_empty() {
if let Some(pos) = colon_pos {
let key = current[..pos].trim().to_string();
let value = current[pos + 1..].trim().to_string();
params.push((key, value));
} else {
bail!("Invalid parameter format");
}
}
Ok(params)
}
fn get_param_u16(params: &[(String, String)], name: &str) -> Result<u16> {
for (key, value) in params {
if key == name {
return value
.parse::<u16>()
.map_err(|_| anyhow!("Invalid u16 value for {}: {}", name, value));
}
}
bail!("Missing parameter: {}", name);
}
fn get_param_bool(params: &[(String, String)], name: &str) -> Result<bool> {
for (key, value) in params {
if key == name {
return value
.parse::<bool>()
.map_err(|_| anyhow!("Invalid bool value for {}: {}", name, value));
}
}
bail!("Missing parameter: {}", name);
}
pub fn parse_loop_mode(text: &str) -> Result<LoopMode> {
match text {
"Any" => Ok(LoopMode::Any),
"Every" => Ok(LoopMode::Every),
"ForEach" => Ok(LoopMode::ForEach),
// Keep backwards compatibility for now
"Existential" => Ok(LoopMode::Any),
"Universal" => Ok(LoopMode::Every),
"Collect" => Ok(LoopMode::ForEach),
// Legacy comprehension modes now map to ForEach since we use dedicated comprehension instructions
"ArrayComprehension" => Ok(LoopMode::ForEach),
"SetComprehension" => Ok(LoopMode::ForEach),
"ObjectComprehension" => Ok(LoopMode::ForEach),
_ => bail!("Invalid loop mode: {}", text),
}
}
// Individual instruction parsers
fn parse_load(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let literal_idx = get_param_u16(&params, "literal_idx")?;
Ok(Instruction::Load {
dest: dest.try_into().unwrap(),
literal_idx,
})
}
fn parse_move(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let src = get_param_u16(&params, "src")?;
Ok(Instruction::Move {
dest: dest.try_into().unwrap(),
src: src.try_into().unwrap(),
})
}
fn parse_add(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let left = get_param_u16(&params, "left")?;
let right = get_param_u16(&params, "right")?;
Ok(Instruction::Add {
dest: dest.try_into().unwrap(),
left: left.try_into().unwrap(),
right: right.try_into().unwrap(),
})
}
fn parse_sub(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let left = get_param_u16(&params, "left")?;
let right = get_param_u16(&params, "right")?;
Ok(Instruction::Sub {
dest: dest.try_into().unwrap(),
left: left.try_into().unwrap(),
right: right.try_into().unwrap(),
})
}
fn parse_mul(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let left = get_param_u16(&params, "left")?;
let right = get_param_u16(&params, "right")?;
Ok(Instruction::Mul {
dest: dest.try_into().unwrap(),
left: left.try_into().unwrap(),
right: right.try_into().unwrap(),
})
}
fn parse_div(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let left = get_param_u16(&params, "left")?;
let right = get_param_u16(&params, "right")?;
Ok(Instruction::Div {
dest: dest.try_into().unwrap(),
left: left.try_into().unwrap(),
right: right.try_into().unwrap(),
})
}
fn parse_eq(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let left = get_param_u16(&params, "left")?;
let right = get_param_u16(&params, "right")?;
Ok(Instruction::Eq {
dest: dest.try_into().unwrap(),
left: left.try_into().unwrap(),
right: right.try_into().unwrap(),
})
}
fn parse_ne_instruction(content: &str) -> Result<Instruction> {
let params = parse_params(content)?;
let dest = get_param_u16(&params, "dest")?;
let left = get_param_u16(&params, "left")?;
let right = get_param_u16(&params, "right")?;
Ok(Instruction::Ne {
dest: dest.try_into().unwrap(),
left: left.try_into().unwrap(),
right: right.try_into().unwrap(),
})
}
fn parse_lt(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let left = get_param_u16(&params, "left")?;
let right = get_param_u16(&params, "right")?;
Ok(Instruction::Lt {
dest: dest.try_into().unwrap(),
left: left.try_into().unwrap(),
right: right.try_into().unwrap(),
})
}
fn parse_le_instruction(content: &str) -> Result<Instruction> {
let params = parse_params(content)?;
let dest = get_param_u16(&params, "dest")?;
let left = get_param_u16(&params, "left")?;
let right = get_param_u16(&params, "right")?;
Ok(Instruction::Le {
dest: dest.try_into().unwrap(),
left: left.try_into().unwrap(),
right: right.try_into().unwrap(),
})
}
fn parse_gt(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let left = get_param_u16(&params, "left")?;
let right = get_param_u16(&params, "right")?;
Ok(Instruction::Gt {
dest: dest.try_into().unwrap(),
left: left.try_into().unwrap(),
right: right.try_into().unwrap(),
})
}
fn parse_ge_instruction(content: &str) -> Result<Instruction> {
let params = parse_params(content)?;
let dest = get_param_u16(&params, "dest")?;
let left = get_param_u16(&params, "left")?;
let right = get_param_u16(&params, "right")?;
Ok(Instruction::Ge {
dest: dest.try_into().unwrap(),
left: left.try_into().unwrap(),
right: right.try_into().unwrap(),
})
}
fn parse_return(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let value = get_param_u16(&params, "value")?;
Ok(Instruction::Return {
value: value.try_into().unwrap(),
})
}
fn parse_rule_init(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let result_reg = get_param_u16(&params, "result_reg")?;
let rule_index = get_param_u16(&params, "rule_index")?;
Ok(Instruction::RuleInit {
result_reg: result_reg.try_into().unwrap(),
rule_index,
})
}
fn parse_rule_return(params_text: &str) -> Result<Instruction> {
let _params = parse_params(params_text)?;
Ok(Instruction::RuleReturn {})
}
fn parse_destructuring_success(params_text: &str) -> Result<Instruction> {
let _params = parse_params(params_text)?;
Ok(Instruction::DestructuringSuccess {})
}
fn parse_object_set(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let obj = get_param_u16(&params, "obj")?;
let key = get_param_u16(&params, "key")?;
let value = get_param_u16(&params, "value")?;
Ok(Instruction::ObjectSet {
obj: obj.try_into().unwrap(),
key: key.try_into().unwrap(),
value: value.try_into().unwrap(),
})
}
fn parse_object_create(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let params_index = get_param_u16(&params, "params_index")?;
Ok(Instruction::ObjectCreate { params_index })
}
fn parse_index(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let container = get_param_u16(&params, "container")?;
let key = get_param_u16(&params, "key")?;
Ok(Instruction::Index {
dest: dest.try_into().unwrap(),
container: container.try_into().unwrap(),
key: key.try_into().unwrap(),
})
}
fn parse_index_literal(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let container = get_param_u16(&params, "container")?;
let literal_idx = get_param_u16(&params, "literal_idx")?;
Ok(Instruction::IndexLiteral {
dest: dest.try_into().unwrap(),
container: container.try_into().unwrap(),
literal_idx,
})
}
fn parse_chained_index(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let params_index = get_param_u16(&params, "params_index")?;
Ok(Instruction::ChainedIndex { params_index })
}
fn parse_array_new(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
Ok(Instruction::ArrayNew {
dest: dest.try_into().unwrap(),
})
}
fn parse_array_push(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let arr = get_param_u16(&params, "arr")?;
let value = get_param_u16(&params, "value")?;
Ok(Instruction::ArrayPush {
arr: arr.try_into().unwrap(),
value: value.try_into().unwrap(),
})
}
fn parse_array_create(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let params_index = get_param_u16(&params, "params_index")?;
Ok(Instruction::ArrayCreate { params_index })
}
fn parse_set_create(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let params_index = get_param_u16(&params, "params_index")?;
Ok(Instruction::SetCreate { params_index })
}
fn parse_set_new(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
Ok(Instruction::SetNew {
dest: dest.try_into().unwrap(),
})
}
fn parse_set_add(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let set = get_param_u16(&params, "set")?;
let value = get_param_u16(&params, "value")?;
Ok(Instruction::SetAdd {
set: set.try_into().unwrap(),
value: value.try_into().unwrap(),
})
}
fn parse_contains(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let collection = get_param_u16(&params, "collection")?;
let value = get_param_u16(&params, "value")?;
Ok(Instruction::Contains {
dest: dest.try_into().unwrap(),
collection: collection.try_into().unwrap(),
value: value.try_into().unwrap(),
})
}
fn parse_count(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let collection = get_param_u16(&params, "collection")?;
Ok(Instruction::Count {
dest: dest.try_into().unwrap(),
collection: collection.try_into().unwrap(),
})
}
fn parse_assert_condition(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let condition = get_param_u16(&params, "condition")?;
Ok(Instruction::AssertCondition {
condition: condition.try_into().unwrap(),
})
}
fn parse_assert_not_undefined(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let register = get_param_u16(&params, "register")?;
Ok(Instruction::AssertNotUndefined {
register: register.try_into().unwrap(),
})
}
fn parse_loop_start(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
// Get params_index parameter - this should be specified in the test
let params_index = get_param_u16(&params, "params_index")?;
Ok(Instruction::LoopStart { params_index })
}
fn parse_loop_next(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let body_start = get_param_u16(&params, "body_start")?;
let loop_end = get_param_u16(&params, "loop_end")?;
Ok(Instruction::LoopNext {
body_start,
loop_end,
})
}
fn parse_load_true(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
Ok(Instruction::LoadTrue {
dest: dest.try_into().unwrap(),
})
}
fn parse_load_false(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
Ok(Instruction::LoadFalse {
dest: dest.try_into().unwrap(),
})
}
fn parse_load_null(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
Ok(Instruction::LoadNull {
dest: dest.try_into().unwrap(),
})
}
fn parse_load_bool(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let value = get_param_bool(&params, "value")?;
Ok(Instruction::LoadBool {
dest: dest.try_into().unwrap(),
value,
})
}
fn parse_load_data(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
Ok(Instruction::LoadData {
dest: dest.try_into().unwrap(),
})
}
fn parse_load_input(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
Ok(Instruction::LoadInput {
dest: dest.try_into().unwrap(),
})
}
fn parse_mod(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let left = get_param_u16(&params, "left")?;
let right = get_param_u16(&params, "right")?;
Ok(Instruction::Mod {
dest: dest.try_into().unwrap(),
left: left.try_into().unwrap(),
right: right.try_into().unwrap(),
})
}
fn parse_and(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let left = get_param_u16(&params, "left")?;
let right = get_param_u16(&params, "right")?;
Ok(Instruction::And {
dest: dest.try_into().unwrap(),
left: left.try_into().unwrap(),
right: right.try_into().unwrap(),
})
}
fn parse_or(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let left = get_param_u16(&params, "left")?;
let right = get_param_u16(&params, "right")?;
Ok(Instruction::Or {
dest: dest.try_into().unwrap(),
left: left.try_into().unwrap(),
right: right.try_into().unwrap(),
})
}
fn parse_not(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let operand = get_param_u16(&params, "operand")?;
Ok(Instruction::Not {
dest: dest.try_into().unwrap(),
operand: operand.try_into().unwrap(),
})
}
fn parse_builtin_call(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let params_index = get_param_u16(&params, "params_index")?;
Ok(Instruction::BuiltinCall { params_index })
}
fn parse_function_call(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let params_index = get_param_u16(&params, "params_index")?;
Ok(Instruction::FunctionCall { params_index })
}
fn parse_call_rule(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let rule_index = get_param_u16(&params, "rule_index")?;
Ok(Instruction::CallRule {
dest: dest.try_into().unwrap(),
rule_index,
})
}
fn parse_virtual_data_document_lookup(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let params_index = get_param_u16(&params, "params_index")?;
Ok(Instruction::VirtualDataDocumentLookup { params_index })
}
fn parse_host_await(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest = get_param_u16(&params, "dest")?;
let arg = get_param_u16(&params, "arg")?;
let id = get_param_u16(&params, "id")?;
Ok(Instruction::HostAwait {
dest: dest.try_into().unwrap(),
arg: arg.try_into().unwrap(),
id: id.try_into().unwrap(),
})
}
fn parse_comprehension_start(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let params_index = get_param_u16(&params, "params_index")?;
Ok(Instruction::ComprehensionBegin { params_index })
}
fn parse_comprehension_add(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let value_reg = get_param_u16(&params, "value_reg")?;
let key_reg = if let Ok(key) = get_param_u16(&params, "key_reg") {
Some(key.try_into().unwrap())
} else {
None
};
Ok(Instruction::ComprehensionYield {
value_reg: value_reg.try_into().unwrap(),
key_reg,
})
}

12
src/rvm/tests/mod.rs Normal file
View File

@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//! RVM test modules
#[cfg(test)]
pub mod instruction_parser;
pub mod test_utils;
#[cfg(test)]
pub mod vm;

104
src/rvm/tests/test_utils.rs Normal file
View File

@@ -0,0 +1,104 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//! Test utility functions for RVM serialization
use crate::rvm::program::{binaries_to_values, BinaryValue, Program};
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use bincode::config::standard;
use bincode::serde::decode_from_slice;
/// Test utility function for round-trip serialization
/// Serializes program, deserializes it, and serializes again to check for consistency
pub fn test_round_trip_serialization(program: &Program) -> Result<(), String> {
// First serialization
let serialized1 = program.serialize_binary()?;
// Basic validation: ensure literal section decodes cleanly under the current format.
if serialized1.len() >= 8 && serialized1.starts_with(&Program::MAGIC) {
let version = u32::from_le_bytes([
serialized1[4],
serialized1[5],
serialized1[6],
serialized1[7],
]);
if version == 2 && serialized1.len() >= 25 {
let entry_points_len = u32::from_le_bytes([
serialized1[8],
serialized1[9],
serialized1[10],
serialized1[11],
]) as usize;
let sources_len = u32::from_le_bytes([
serialized1[12],
serialized1[13],
serialized1[14],
serialized1[15],
]) as usize;
let literals_len = u32::from_le_bytes([
serialized1[16],
serialized1[17],
serialized1[18],
serialized1[19],
]) as usize;
let entry_points_start = 25;
let sources_start = entry_points_start + entry_points_len;
let literals_start = sources_start + sources_len;
let rule_tree_start = literals_start + literals_len;
if literals_len > 0 && serialized1.len() >= rule_tree_start {
match decode_from_slice::<Vec<BinaryValue>, _>(
&serialized1[literals_start..rule_tree_start],
standard(),
) {
Ok((decoded_literals, _)) => {
if binaries_to_values(decoded_literals).is_err() {
return Err(
"Failed to convert literal table from binary representation".into(),
);
}
}
Err(err) => {
return Err(format!(
"Failed to decode literal table with bincode: {}",
err
));
}
}
}
}
}
// Deserialize
let deserialized = match Program::deserialize_binary(&serialized1)? {
crate::rvm::program::DeserializationResult::Complete(program) => program,
crate::rvm::program::DeserializationResult::Partial(program) => {
let info = format!(
"Deserialization resulted in partial program during round-trip test \
(instructions={}, literals={}, needs_recompilation={})",
program.instructions.len(),
program.literals.len(),
program.needs_recompilation()
);
return Err(info);
}
};
// Second serialization
let serialized2 = deserialized.serialize_binary()?;
// Compare the two serialized versions
if serialized1 == serialized2 {
Ok(())
} else {
Err(format!(
"Round-trip serialization failed: serialized data differs. \
First serialization: {} bytes, Second: {} bytes",
serialized1.len(),
serialized2.len()
))
}
}

995
src/rvm/tests/vm.rs Normal file
View File

@@ -0,0 +1,995 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#[cfg(test)]
mod tests {
use crate::rvm::tests::instruction_parser::{parse_instruction, parse_loop_mode};
use crate::rvm::tests::test_utils::test_round_trip_serialization;
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
struct RuleInfoSpec {
rule_type: String,
definitions: Vec<Vec<u16>>,
#[serde(default)]
default_rule_index: Option<u16>,
#[serde(default)]
default_literal_index: Option<u16>,
#[serde(default)]
destructuring_blocks: Option<Vec<Option<u16>>>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
struct DefaultRuleSpec {
rule_name: String,
default_value: crate::Value,
}
use crate::rvm::vm::{ExecutionMode, ExecutionState, RegoVM, SuspendReason, VmError};
use crate::tests::interpreter::process_value;
use crate::value::Value;
use alloc::collections::{BTreeMap, VecDeque};
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::fs;
use test_generator::test_resources;
extern crate alloc;
extern crate std;
#[derive(Debug, Clone, Deserialize, Serialize)]
struct HostAwaitResponseSpec {
id: crate::Value,
#[serde(default)]
value: Option<crate::Value>,
#[serde(default)]
values: Vec<crate::Value>,
}
#[derive(Debug, Deserialize, Serialize)]
struct VmTestCase {
note: String,
#[serde(default)]
description: Option<String>,
#[serde(default)]
example_rego: Option<String>,
#[serde(default)]
data: Option<crate::Value>,
#[serde(default)]
input: Option<crate::Value>,
literals: Vec<crate::Value>,
#[serde(default)]
rule_infos: Vec<RuleInfoSpec>,
#[serde(default)]
default_rules: Vec<DefaultRuleSpec>,
#[serde(default)]
rule_tree: Option<crate::Value>,
#[serde(default)]
instruction_params: Option<InstructionParamsSpec>,
#[serde(default)]
max_instructions: Option<usize>,
#[serde(default)]
host_await_responses: Option<Vec<HostAwaitResponseSpec>>,
#[serde(default)]
host_await_responses_run_to_completion: Option<Vec<HostAwaitResponseSpec>>,
#[serde(default)]
host_await_responses_suspendable: Option<Vec<HostAwaitResponseSpec>>,
#[serde(default)]
ignore_run_to_completion_hostawait_failure: bool,
instructions: Vec<String>,
#[serde(default, deserialize_with = "deserialize_optional_value")]
want_result: Option<crate::Value>,
#[serde(default)]
want_error: Option<String>,
#[serde(default, deserialize_with = "deserialize_optional_value")]
want_result_strict: Option<crate::Value>,
#[serde(default)]
want_error_strict: Option<String>,
}
fn deserialize_optional_value<'de, D>(deserializer: D) -> Result<Option<crate::Value>, D::Error>
where
D: serde::Deserializer<'de>,
{
// If the field is present, always return Some, even if the value is null
crate::Value::deserialize(deserializer).map(Some)
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
struct InstructionParamsSpec {
#[serde(default)]
loop_params: Vec<LoopStartParamsSpec>,
#[serde(default)]
call_params: Vec<CallParamsSpec>,
#[serde(default)]
builtin_call_params: Vec<BuiltinCallParamsSpec>,
#[serde(default)]
function_call_params: Vec<FunctionCallParamsSpec>,
#[serde(default)]
builtin_infos: Vec<BuiltinInfoSpec>,
#[serde(default)]
object_create_params: Vec<ObjectCreateParamsSpec>,
#[serde(default)]
array_create_params: Vec<ArrayCreateParamsSpec>,
#[serde(default)]
set_create_params: Vec<SetCreateParamsSpec>,
#[serde(default)]
virtual_data_document_lookup_params: Vec<VirtualDataDocumentLookupParamsSpec>,
#[serde(default)]
chained_index_params: Vec<ChainedIndexParamsSpec>,
#[serde(default, alias = "comprehension_start_params")]
comprehension_begin_params: Vec<ComprehensionBeginParamsSpec>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
struct LoopStartParamsSpec {
mode: String,
collection: u16,
key_reg: u16,
value_reg: u16,
result_reg: u16,
body_start: u16,
loop_end: u16,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
struct CallParamsSpec {
dest: u16,
func: u16,
args_start: u16,
args_count: u16,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
struct BuiltinCallParamsSpec {
dest: u16,
builtin_index: u16,
args: Vec<u16>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
struct FunctionCallParamsSpec {
func: u16,
dest: u16,
args: Vec<u16>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
struct BuiltinInfoSpec {
name: String,
num_args: u16,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
struct ObjectCreateParamsSpec {
dest: u16,
template_literal_idx: u16,
literal_key_fields: Vec<(u16, u16)>,
fields: Vec<(u16, u16)>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
struct ArrayCreateParamsSpec {
dest: u16,
elements: Vec<u16>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
struct SetCreateParamsSpec {
dest: u16,
elements: Vec<u16>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
struct LiteralOrRegisterSpec {
#[serde(default, alias = "literal")]
literal_idx: Option<u16>,
#[serde(default, alias = "reg")]
register: Option<u16>,
}
impl LiteralOrRegisterSpec {
fn into_literal_or_register(self) -> crate::rvm::instructions::LiteralOrRegister {
if let Some(literal_idx) = self.literal_idx {
crate::rvm::instructions::LiteralOrRegister::Literal(literal_idx)
} else if let Some(register) = self.register {
crate::rvm::instructions::LiteralOrRegister::Register(register.try_into().unwrap())
} else {
panic!("LiteralOrRegisterSpec must specify either literal_idx or register");
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
struct VirtualDataDocumentLookupParamsSpec {
dest: u16,
path_components: Vec<LiteralOrRegisterSpec>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
struct ChainedIndexParamsSpec {
dest: u16,
root: u16,
path_components: Vec<LiteralOrRegisterSpec>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
struct ComprehensionBeginParamsSpec {
mode: String,
collection_reg: u16,
#[serde(default)]
result_reg: Option<u16>,
key_reg: u16,
value_reg: u16,
body_start: u16,
comprehension_end: u16,
}
#[derive(Debug, Deserialize, Serialize)]
struct VmTestSuite {
cases: Vec<VmTestCase>,
}
/// Execute VM instructions directly from parsed instructions and literals
#[allow(clippy::too_many_arguments)]
fn execute_vm_instructions(
instructions: Vec<crate::rvm::instructions::Instruction>,
literals: Vec<Value>,
rule_infos: Vec<RuleInfoSpec>,
rule_tree: Option<Value>,
instruction_params: Option<InstructionParamsSpec>,
data: Option<Value>,
input: Option<Value>,
max_instructions: Option<usize>,
host_await_responses: Option<Vec<HostAwaitResponseSpec>>,
host_await_responses_run_to_completion: Option<Vec<HostAwaitResponseSpec>>,
host_await_responses_suspendable: Option<Vec<HostAwaitResponseSpec>>,
ignore_run_to_completion_hostawait_failure: bool,
strict: bool,
) -> Result<Value> {
let processed_data = if let Some(ref data_value) = data {
Some(process_value(data_value)?)
} else {
None
};
let processed_input = if let Some(ref input_value) = input {
Some(process_value(input_value)?)
} else {
None
};
let processed_rule_tree = if let Some(ref tree_value) = rule_tree {
Some(process_value(tree_value)?)
} else {
None
};
let process_responses =
|responses: Vec<HostAwaitResponseSpec>| -> Result<BTreeMap<Value, Vec<Value>>> {
let mut processed: BTreeMap<Value, Vec<Value>> = BTreeMap::new();
for spec in responses {
let identifier = process_value(&spec.id)?;
let mut values: Vec<Value> = Vec::new();
if let Some(single) = spec.value.as_ref() {
values.push(process_value(single)?);
}
for value in &spec.values {
values.push(process_value(value)?);
}
if values.is_empty() {
return Err(anyhow::anyhow!(
"HostAwait response specification for id {:?} has no values",
identifier
));
}
processed.entry(identifier).or_default().extend(values);
}
Ok(processed)
};
let processed_host_responses = if let Some(responses) = host_await_responses {
Some(process_responses(responses)?)
} else {
None
};
let processed_host_responses_run_to_completion =
if let Some(responses) = host_await_responses_run_to_completion {
Some(process_responses(responses)?)
} else {
processed_host_responses.clone()
};
let processed_host_responses_suspendable =
if let Some(responses) = host_await_responses_suspendable {
Some(process_responses(responses)?)
} else {
processed_host_responses.clone()
};
// Create a Program from instructions and literals
let mut program = crate::rvm::program::Program::new();
program.instructions = instructions;
// Process literals through the value converter to handle special syntax like set!
let mut processed_literals = Vec::new();
for literal in &literals {
processed_literals.push(process_value(literal)?);
}
program.literals = processed_literals;
if let Some(tree) = processed_rule_tree {
program.rule_tree = tree;
} else {
program.rule_tree = Value::new_object();
}
// Convert rule infos
for rule_info_spec in rule_infos.iter() {
use crate::rvm::program::{RuleInfo, RuleType};
let rule_type = match rule_info_spec.rule_type.as_str() {
"Complete" => RuleType::Complete,
"PartialSet" => RuleType::PartialSet,
"PartialObject" => RuleType::PartialObject,
_ => {
return Err(anyhow::anyhow!(
"Unknown rule type: {}",
rule_info_spec.rule_type
))
}
};
// Convert Vec<Vec<u16>> to Vec<Vec<u32>>
let definitions: Vec<Vec<u32>> = rule_info_spec
.definitions
.iter()
.map(|def| def.iter().map(|&x| x as u32).collect())
.collect();
let mut destructuring_blocks: Vec<Option<u32>> = rule_info_spec
.destructuring_blocks
.clone()
.map(|blocks| {
blocks
.into_iter()
.map(|entry| entry.map(|value| value as u32))
.collect()
})
.unwrap_or_else(|| alloc::vec![None; definitions.len()]);
if destructuring_blocks.len() != definitions.len() {
destructuring_blocks.resize(definitions.len(), None);
}
// For function calls, use result_reg 0; for other rules, use result_reg 1
let result_reg = if instruction_params
.as_ref()
.is_some_and(|params| !params.function_call_params.is_empty())
{
0 // Function calls use register 0 as return register
} else {
1 // Regular rules use register 1
};
let rule_info = RuleInfo {
name: String::from("test_rule"),
rule_type,
definitions: crate::Rc::new(definitions.clone()),
function_info: None,
default_literal_index: rule_info_spec.default_literal_index,
result_reg,
num_registers: 50, // Increased to accommodate test cases with higher register indices
destructuring_blocks,
};
program.rule_infos.push(rule_info);
}
// Build instruction data from params specification
if let Some(params_spec) = instruction_params {
// Convert loop params
for loop_param_spec in params_spec.loop_params {
let mode = parse_loop_mode(&loop_param_spec.mode)?;
let loop_params = crate::rvm::instructions::LoopStartParams {
mode,
collection: loop_param_spec.collection.try_into().unwrap(),
key_reg: loop_param_spec.key_reg.try_into().unwrap(),
value_reg: loop_param_spec.value_reg.try_into().unwrap(),
result_reg: loop_param_spec.result_reg.try_into().unwrap(),
body_start: loop_param_spec.body_start,
loop_end: loop_param_spec.loop_end,
};
program.add_loop_params(loop_params);
}
// Convert call params
// Legacy call_params support removed - use builtin_call_params or function_call_params instead
if !params_spec.call_params.is_empty() {
// Legacy call parameters are no longer supported
// Convert to BuiltinCall or FunctionCall instructions instead
panic!("Legacy call_params are no longer supported. Use builtin_call_params or function_call_params instead.");
}
// Convert builtin info specs to program builtin info table
for builtin_info_spec in params_spec.builtin_infos {
let builtin_info = crate::rvm::program::BuiltinInfo {
name: builtin_info_spec.name,
num_args: builtin_info_spec.num_args,
};
program.add_builtin_info(builtin_info);
}
// Convert builtin call params
for builtin_call_spec in params_spec.builtin_call_params {
use crate::rvm::instructions::BuiltinCallParams;
// Convert Vec<u16> to fixed array (unused slots are irrelevant due to num_args)
let mut args_array = [0u8; 8];
for (i, &arg) in builtin_call_spec.args.iter().enumerate() {
if i < 8 {
args_array[i] = arg.try_into().unwrap();
}
}
let builtin_call_params = BuiltinCallParams {
dest: builtin_call_spec.dest.try_into().unwrap(),
builtin_index: builtin_call_spec.builtin_index,
num_args: builtin_call_spec.args.len() as u8,
args: args_array,
};
program.add_builtin_call_params(builtin_call_params);
}
// Convert function call params
for function_call_spec in params_spec.function_call_params {
use crate::rvm::instructions::FunctionCallParams;
// Convert Vec<u16> to fixed array (unused slots are irrelevant due to num_args)
let mut args_array = [0u8; 8];
for (i, &arg) in function_call_spec.args.iter().enumerate() {
if i < 8 {
args_array[i] = arg.try_into().unwrap();
}
}
let function_call_params = FunctionCallParams {
func_rule_index: function_call_spec.func,
dest: function_call_spec.dest.try_into().unwrap(),
num_args: function_call_spec.args.len() as u8,
args: args_array,
};
program.add_function_call_params(function_call_params);
}
// Convert object create params
for object_create_spec in params_spec.object_create_params {
use crate::rvm::instructions::ObjectCreateParams;
let object_create_params = ObjectCreateParams {
dest: object_create_spec.dest.try_into().unwrap(),
template_literal_idx: object_create_spec.template_literal_idx,
literal_key_fields: object_create_spec
.literal_key_fields
.into_iter()
.map(|(k, v)| (k, v.try_into().unwrap()))
.collect(),
fields: object_create_spec
.fields
.into_iter()
.map(|(k, v)| (k.try_into().unwrap(), v.try_into().unwrap()))
.collect(),
};
program
.instruction_data
.add_object_create_params(object_create_params);
}
// Convert array create params
for array_create_spec in params_spec.array_create_params {
use crate::rvm::instructions::ArrayCreateParams;
let array_create_params = ArrayCreateParams {
dest: array_create_spec.dest.try_into().unwrap(),
elements: array_create_spec
.elements
.into_iter()
.map(|reg| reg.try_into().unwrap())
.collect(),
};
program
.instruction_data
.add_array_create_params(array_create_params);
}
// Convert set create params
for set_create_spec in params_spec.set_create_params {
use crate::rvm::instructions::SetCreateParams;
let set_create_params = SetCreateParams {
dest: set_create_spec.dest.try_into().unwrap(),
elements: set_create_spec
.elements
.into_iter()
.map(|reg| reg.try_into().unwrap())
.collect(),
};
program
.instruction_data
.add_set_create_params(set_create_params);
}
// Convert virtual data document lookup params
for virtual_spec in params_spec.virtual_data_document_lookup_params {
use crate::rvm::instructions::{
LiteralOrRegister, VirtualDataDocumentLookupParams,
};
let path_components: Vec<LiteralOrRegister> = virtual_spec
.path_components
.into_iter()
.map(|component| component.into_literal_or_register())
.collect();
let params = VirtualDataDocumentLookupParams {
dest: virtual_spec.dest.try_into().unwrap(),
path_components,
};
program
.instruction_data
.add_virtual_data_document_lookup_params(params);
}
// Convert chained index params
for chained_spec in params_spec.chained_index_params {
use crate::rvm::instructions::{ChainedIndexParams, LiteralOrRegister};
let path_components: Vec<LiteralOrRegister> = chained_spec
.path_components
.into_iter()
.map(|component| component.into_literal_or_register())
.collect();
let params = ChainedIndexParams {
dest: chained_spec.dest.try_into().unwrap(),
root: chained_spec.root.try_into().unwrap(),
path_components,
};
program.instruction_data.add_chained_index_params(params);
}
// Convert comprehension start params
for comprehension_spec in params_spec.comprehension_begin_params {
use crate::rvm::instructions::{ComprehensionBeginParams, ComprehensionMode};
let mode = match comprehension_spec.mode.as_str() {
"Array" => ComprehensionMode::Array,
"Set" => ComprehensionMode::Set,
"Object" => ComprehensionMode::Object,
_ => panic!("Invalid comprehension mode: {}", comprehension_spec.mode),
};
let comprehension_params = ComprehensionBeginParams {
mode,
collection_reg: comprehension_spec.collection_reg.try_into().unwrap(),
result_reg: comprehension_spec
.result_reg
.unwrap_or(comprehension_spec.collection_reg)
.try_into()
.unwrap(),
key_reg: comprehension_spec.key_reg.try_into().unwrap(),
value_reg: comprehension_spec.value_reg.try_into().unwrap(),
body_start: comprehension_spec.body_start,
comprehension_end: comprehension_spec.comprehension_end,
};
program
.instruction_data
.add_comprehension_begin_params(comprehension_params);
}
}
program.main_entry_point = 0;
// Set a reasonable default for register window size in VM tests
// Most tests use registers 0-10, so we'll allocate 256 registers to be safe
program.max_rule_window_size = 256;
program.dispatch_window_size = 50;
// Initialize resolved builtins if we have builtin info
if !program.builtin_info_table.is_empty() {
if let Err(e) = program.initialize_resolved_builtins() {
return Err(anyhow::anyhow!(
"Failed to initialize resolved builtins: {}",
e
));
}
}
// Ensure program artifacts survive binary round-tripping
test_round_trip_serialization(&program)
.map_err(|e| anyhow::anyhow!("Program serialization round-trip failed: {}", e))?;
let program = Arc::new(program);
let run_with_mode = |mode: ExecutionMode,
use_step_mode: bool,
host_responses_template: Option<BTreeMap<Value, Vec<Value>>>|
-> Result<Result<Value>> {
let mut vm = RegoVM::new();
vm.set_execution_mode(mode);
vm.set_step_mode(use_step_mode);
vm.set_strict_builtin_errors(strict);
if let Some(data_value) = processed_data.clone() {
vm.set_data(data_value)?;
}
if let Some(input_value) = processed_input.clone() {
vm.set_input(input_value);
}
if let Some(limit) = max_instructions {
vm.set_max_instructions(limit);
}
if matches!(mode, ExecutionMode::RunToCompletion) {
if let Some(responses) = host_responses_template.clone() {
vm.set_host_await_responses(responses);
}
}
vm.load_program(program.clone());
let mut response_map = host_responses_template.clone().map(|map| {
map.into_iter()
.map(|(identifier, values)| (identifier, VecDeque::from(values)))
.collect::<BTreeMap<_, _>>()
});
let mut last_result = vm.execute().map_err(|e| anyhow::anyhow!("{}", e));
loop {
match vm.execution_state() {
ExecutionState::Completed { result } => {
return Ok(Ok(result.clone()));
}
ExecutionState::Error { error } => {
return Ok(Err(anyhow::anyhow!("{}", error)));
}
ExecutionState::Suspended { reason, .. } => {
if mode != ExecutionMode::Suspendable {
return Ok(Err(anyhow::anyhow!(
"Run-to-completion execution unexpectedly suspended: {:?}",
reason
)));
}
match reason {
SuspendReason::HostAwait {
dest, identifier, ..
} => {
let dest = *dest;
let identifier = identifier.clone();
let response = {
let map = response_map.as_mut().ok_or_else(|| {
anyhow::anyhow!(
"{}",
VmError::HostAwaitResponseMissing {
dest,
identifier: identifier.clone(),
}
)
})?;
let queue = map.get_mut(&identifier).ok_or_else(|| {
anyhow::anyhow!(
"{}",
VmError::HostAwaitResponseMissing {
dest,
identifier: identifier.clone(),
}
)
})?;
let response = queue.pop_front().ok_or_else(|| {
anyhow::anyhow!(
"{}",
VmError::HostAwaitResponseMissing {
dest,
identifier: identifier.clone(),
}
)
})?;
if queue.is_empty() {
map.remove(&identifier);
}
response
};
last_result = vm
.resume(Some(response))
.map_err(|e| anyhow::anyhow!("{}", e));
}
SuspendReason::Step => {
if !use_step_mode {
return Ok(Err(anyhow::anyhow!(
"Suspendable execution unexpectedly suspended: {:?}",
reason
)));
}
last_result = vm.resume(None).map_err(|e| anyhow::anyhow!("{}", e));
}
_ => {
last_result = vm.resume(None).map_err(|e| anyhow::anyhow!("{}", e));
}
}
}
_ => match &last_result {
Ok(value) => return Ok(Ok(value.clone())),
Err(err) => return Ok(Err(anyhow::anyhow!("{}", err))),
},
}
}
};
let compare_results = |baseline_name: &str,
baseline: &Result<Value>,
other_name: &str,
other: &Result<Value>|
-> Result<()> {
match (baseline, other) {
(Ok(expected), Ok(actual)) => {
if expected != actual {
return Err(anyhow::anyhow!(
"{} execution result {:?} differed from {} {:?}",
other_name,
actual,
baseline_name,
expected
));
}
Ok(())
}
(Err(expected_err), Err(other_err)) => {
let expected_msg = expected_err.to_string();
let other_msg = other_err.to_string();
if expected_msg != other_msg {
return Err(anyhow::anyhow!(
"{} execution error '{}' differed from {} '{}'",
other_name,
other_msg,
baseline_name,
expected_msg
));
}
Ok(())
}
(Ok(expected), Err(other_err)) => Err(anyhow::anyhow!(
"{} execution failed with '{}' while {} succeeded with {:?}",
other_name,
other_err,
baseline_name,
expected
)),
(Err(expected_err), Ok(actual)) => Err(anyhow::anyhow!(
"{} execution succeeded with {:?} while {} failed with '{}'",
other_name,
actual,
baseline_name,
expected_err
)),
}
};
let run_to_completion = run_with_mode(
ExecutionMode::RunToCompletion,
false,
processed_host_responses_run_to_completion.clone(),
)?;
let suspendable = run_with_mode(
ExecutionMode::Suspendable,
false,
processed_host_responses_suspendable.clone(),
)?;
let stepwise = run_with_mode(
ExecutionMode::Suspendable,
true,
processed_host_responses_suspendable.clone(),
)?;
const HOST_AWAIT_RESPONSE_MISSING: &str = "HostAwait executed but no response provided";
let ignore_run_to_completion = ignore_run_to_completion_hostawait_failure
&& matches!(
&run_to_completion,
Err(err) if err.to_string().contains(HOST_AWAIT_RESPONSE_MISSING)
);
if ignore_run_to_completion {
compare_results("suspendable", &suspendable, "step-by-step", &stepwise)?;
return suspendable;
}
compare_results(
"run-to-completion",
&run_to_completion,
"suspendable",
&suspendable,
)?;
compare_results(
"run-to-completion",
&run_to_completion,
"step-by-step",
&stepwise,
)?;
run_to_completion
}
fn run_vm_test_suite(file: &str) -> Result<()> {
std::println!("Running VM test suite: {}", file);
let yaml_content = fs::read_to_string(file)?;
let test_suite: VmTestSuite = serde_yaml::from_str(&yaml_content)?;
for test_case in test_suite.cases {
std::println!("Running VM test case: {}", test_case.note);
let instructions = test_case
.instructions
.iter()
.map(|instruction_str| parse_instruction(instruction_str))
.collect::<Result<Vec<_>>>()?;
let ignore_hostawait_failure = test_case.ignore_run_to_completion_hostawait_failure;
struct ModeExpectation<'a> {
strict: bool,
want_result: Option<&'a crate::Value>,
want_error: Option<&'a String>,
}
let mut expectations = Vec::new();
if test_case.want_result.is_some() || test_case.want_error.is_some() {
expectations.push(ModeExpectation {
strict: false,
want_result: test_case.want_result.as_ref(),
want_error: test_case.want_error.as_ref(),
});
}
if test_case.want_result_strict.is_some() || test_case.want_error_strict.is_some() {
expectations.push(ModeExpectation {
strict: true,
want_result: test_case.want_result_strict.as_ref(),
want_error: test_case.want_error_strict.as_ref(),
});
}
if expectations.is_empty() {
panic!(
"Test case '{}' must specify expectations for at least one mode",
test_case.note
);
}
for expectation in expectations {
let mode_label = if expectation.strict {
"strict"
} else {
"non-strict"
};
std::println!(" Mode: {}", mode_label);
let execution_result = execute_vm_instructions(
instructions.clone(),
test_case.literals.clone(),
test_case.rule_infos.clone(),
test_case.rule_tree.clone(),
test_case.instruction_params.clone(),
test_case.data.clone(),
test_case.input.clone(),
test_case.max_instructions,
test_case.host_await_responses.clone(),
test_case.host_await_responses_run_to_completion.clone(),
test_case.host_await_responses_suspendable.clone(),
ignore_hostawait_failure,
expectation.strict,
);
if expectation.want_error.is_some() && expectation.want_result.is_some() {
panic!(
"Test case '{}' cannot specify both want_result and want_error for {} mode",
test_case.note, mode_label
);
}
if let Some(expected_error) = expectation.want_error {
match execution_result {
Err(e) => {
let error_msg = std::format!("{}", e);
if !error_msg.contains(expected_error) {
std::println!(
"Test case '{}' failed ({} mode):",
test_case.note,
mode_label
);
std::println!(" Expected error containing: '{}'", expected_error);
std::println!(" Actual error: '{}'", error_msg);
panic!("VM test case failed: {}", test_case.note);
}
}
Ok(result) => {
std::println!(
"Test case '{}' failed ({} mode):",
test_case.note,
mode_label
);
std::println!(" Expected error containing: '{}'", expected_error);
std::println!(" But got successful result: {:?}", result);
panic!("VM test case failed: {}", test_case.note);
}
}
} else if let Some(want_result) = expectation.want_result {
let expected_result = process_value(want_result)?;
let actual_result = match execution_result {
Ok(result) => result,
Err(e) => {
if std::format!("{}", e).contains("Assertion failed") {
Value::Undefined
} else {
return Err(e);
}
}
};
if actual_result != expected_result {
std::println!(
"Test case '{}' failed ({} mode):",
test_case.note,
mode_label
);
std::println!(" Expected: {:?}", expected_result);
std::println!(" Actual: {:?}", actual_result);
panic!("VM test case failed: {}", test_case.note);
}
} else {
panic!(
"Test case '{}' must specify either want_result or want_error for {} mode",
test_case.note, mode_label
);
}
std::println!("{} mode passed", mode_label);
}
std::println!("✓ Test case '{}' passed", test_case.note);
}
std::println!("✓ Test suite '{}' completed successfully", file);
Ok(())
}
#[test_resources("tests/rvm/vm/suites/*.yaml")]
fn run_vm_test_file(file: &str) {
run_vm_test_suite(file).unwrap()
}
#[test_resources("tests/rvm/vm/suites/loops/*.yaml")]
fn run_loop_test_file(file: &str) {
run_vm_test_suite(file).unwrap()
}
}

101
src/rvm/vm/arithmetic.rs Normal file
View File

@@ -0,0 +1,101 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::number::Number;
use crate::value::Value;
use super::errors::{Result, VmError};
use super::machine::RegoVM;
impl RegoVM {
/// Add two values using interpreter's arithmetic logic
pub(super) fn add_values(&self, a: &Value, b: &Value) -> Result<Value> {
match (a, b) {
(Value::Number(x), Value::Number(y)) => Ok(Value::from(x.add(y)?)),
_ => Err(VmError::InvalidAddition {
left: a.clone(),
right: b.clone(),
}),
}
}
/// Subtract two values using interpreter's arithmetic logic
pub(super) fn sub_values(&self, a: &Value, b: &Value) -> Result<Value> {
match (a, b) {
(Value::Number(x), Value::Number(y)) => Ok(Value::from(x.sub(y)?)),
_ => Err(VmError::InvalidSubtraction {
left: a.clone(),
right: b.clone(),
}),
}
}
/// Multiply two values using interpreter's arithmetic logic
pub(super) fn mul_values(&self, a: &Value, b: &Value) -> Result<Value> {
match (a, b) {
(Value::Number(x), Value::Number(y)) => Ok(Value::from(x.mul(y)?)),
_ => Err(VmError::InvalidMultiplication {
left: a.clone(),
right: b.clone(),
}),
}
}
/// Divide two values using interpreter's arithmetic logic
pub(super) fn div_values(&self, a: &Value, b: &Value) -> Result<Value> {
match (a, b) {
(Value::Number(x), Value::Number(y)) => {
if *y == Number::from(0u64) {
if self.strict_builtin_errors {
return Err(VmError::InvalidDivision {
left: a.clone(),
right: b.clone(),
});
}
return Ok(Value::Undefined);
}
Ok(Value::from(x.clone().divide(y)?))
}
_ => Err(VmError::InvalidDivision {
left: a.clone(),
right: b.clone(),
}),
}
}
/// Modulo two values using interpreter's arithmetic logic
pub(super) fn mod_values(&self, a: &Value, b: &Value) -> Result<Value> {
match (a, b) {
(Value::Number(x), Value::Number(y)) => {
if *y == Number::from(0u64) {
if self.strict_builtin_errors {
return Err(VmError::InvalidModulo {
left: a.clone(),
right: b.clone(),
});
}
return Ok(Value::Undefined);
}
if !x.is_integer() || !y.is_integer() {
return Err(VmError::ModuloOnFloat);
}
Ok(Value::from(x.clone().modulo(y)?))
}
_ => Err(VmError::InvalidModulo {
left: a.clone(),
right: b.clone(),
}),
}
}
pub(super) fn to_bool(&self, value: &Value) -> Option<bool> {
match value {
Value::Bool(b) => Some(*b),
Value::Null if !self.strict_builtin_errors => Some(true),
_ => None,
}
}
}

543
src/rvm/vm/comprehension.rs Normal file
View File

@@ -0,0 +1,543 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::rvm::instructions::{ComprehensionBeginParams, ComprehensionMode};
use crate::value::Value;
use crate::Rc;
use alloc::collections::BTreeMap;
use alloc::format;
use alloc::sync::Arc;
use alloc::vec::Vec;
use super::context::{ComprehensionContext, IterationState};
use super::errors::{Result, VmError};
use super::execution_model::{ExecutionFrame, ExecutionMode, FrameKind};
use super::machine::RegoVM;
impl RegoVM {
pub(super) fn execute_comprehension_begin(
&mut self,
params: &ComprehensionBeginParams,
) -> Result<()> {
match self.execution_mode {
ExecutionMode::RunToCompletion => {
self.execute_comprehension_begin_run_to_completion(params)
}
ExecutionMode::Suspendable => self.execute_comprehension_begin_suspendable(params),
}
}
fn execute_comprehension_begin_run_to_completion(
&mut self,
params: &ComprehensionBeginParams,
) -> Result<()> {
let initial_result = match params.mode {
ComprehensionMode::Set => Value::new_set(),
ComprehensionMode::Array => Value::new_array(),
ComprehensionMode::Object => Value::Object(Rc::new(BTreeMap::new())),
};
self.registers[params.result_reg as usize] = initial_result.clone();
let auto_iterate = params.collection_reg != params.result_reg;
let iteration_state = if auto_iterate {
let source_value = self.registers[params.collection_reg as usize].clone();
match source_value {
Value::Array(items) => {
if items.is_empty() {
None
} else {
Some(IterationState::Array { items, index: 0 })
}
}
Value::Object(obj) => {
if obj.is_empty() {
None
} else {
Some(IterationState::Object {
obj,
current_key: None,
first_iteration: true,
})
}
}
Value::Set(set) => {
if set.is_empty() {
None
} else {
Some(IterationState::Set {
items: set,
current_item: None,
first_iteration: true,
})
}
}
Value::Undefined => None,
Value::Null => None,
_ => None,
}
} else {
None
};
let mut has_iteration = false;
if let Some(state) = iteration_state.as_ref() {
has_iteration = self.setup_next_iteration(state, params.key_reg, params.value_reg)?;
}
let resume_pc = if auto_iterate {
params.comprehension_end as usize
} else {
params.comprehension_end.saturating_sub(1) as usize
};
let mut comprehension_context = ComprehensionContext {
mode: params.mode.clone(),
result_reg: params.result_reg,
key_reg: params.key_reg,
value_reg: params.value_reg,
body_start: params.body_start,
comprehension_end: params.comprehension_end,
iteration_state,
resume_pc,
};
if auto_iterate {
if has_iteration {
self.pc = params.body_start as usize - 1;
} else {
comprehension_context.iteration_state = None;
self.pc = params.comprehension_end as usize - 1;
}
}
self.comprehension_stack.push(comprehension_context);
Ok(())
}
fn execute_comprehension_begin_suspendable(
&mut self,
params: &ComprehensionBeginParams,
) -> Result<()> {
let initial_result = match params.mode {
ComprehensionMode::Set => Value::new_set(),
ComprehensionMode::Array => Value::new_array(),
ComprehensionMode::Object => Value::Object(Rc::new(BTreeMap::new())),
};
self.registers[params.result_reg as usize] = initial_result.clone();
let auto_iterate = params.collection_reg != params.result_reg;
let iteration_state = if auto_iterate {
let source_value = self.registers[params.collection_reg as usize].clone();
match source_value {
Value::Array(items) => {
if items.is_empty() {
None
} else {
Some(IterationState::Array { items, index: 0 })
}
}
Value::Object(obj) => {
if obj.is_empty() {
None
} else {
Some(IterationState::Object {
obj,
current_key: None,
first_iteration: true,
})
}
}
Value::Set(set) => {
if set.is_empty() {
None
} else {
Some(IterationState::Set {
items: set,
current_item: None,
first_iteration: true,
})
}
}
Value::Undefined => None,
Value::Null => None,
_ => None,
}
} else {
None
};
let has_iteration = if let Some(state) = iteration_state.as_ref() {
self.setup_next_iteration(state, params.key_reg, params.value_reg)?
} else {
false
};
let resume_pc = if auto_iterate {
params.comprehension_end as usize
} else {
params.comprehension_end.saturating_sub(1) as usize
};
let mut comprehension_context = ComprehensionContext {
mode: params.mode.clone(),
result_reg: params.result_reg,
key_reg: params.key_reg,
value_reg: params.value_reg,
body_start: params.body_start,
comprehension_end: params.comprehension_end,
iteration_state,
resume_pc,
};
let next_pc = if auto_iterate {
if has_iteration {
params.body_start as usize
} else {
comprehension_context.iteration_state = None;
params.comprehension_end as usize
}
} else {
self.pc + 1
};
let return_pc = comprehension_context.resume_pc;
let frame = ExecutionFrame::new(
next_pc,
FrameKind::Comprehension {
return_pc,
context: comprehension_context,
},
);
self.execution_stack.push(frame);
Ok(())
}
pub(super) fn execute_comprehension_yield(
&mut self,
value_reg: u8,
key_reg: Option<u8>,
) -> Result<()> {
match self.execution_mode {
ExecutionMode::RunToCompletion => {
self.execute_comprehension_yield_run_to_completion(value_reg, key_reg)
}
ExecutionMode::Suspendable => {
self.execute_comprehension_yield_suspendable(value_reg, key_reg)
}
}
}
fn execute_comprehension_yield_run_to_completion(
&mut self,
value_reg: u8,
key_reg: Option<u8>,
) -> Result<()> {
let mut comprehension_context = if let Some(context) = self.comprehension_stack.pop() {
context
} else {
return Err(VmError::InvalidIteration {
value: Value::String(Arc::from("No active comprehension")),
});
};
let value_to_add = self.registers[value_reg as usize].clone();
let key_value = if let Some(key_reg) = key_reg {
Some(self.registers[key_reg as usize].clone())
} else if matches!(comprehension_context.mode, ComprehensionMode::Object) {
Some(self.registers[comprehension_context.key_reg as usize].clone())
} else {
None
};
let result_reg = comprehension_context.result_reg as usize;
let current_result = self.registers[result_reg].clone();
let mode = comprehension_context.mode.clone();
let updated_result = match (mode, current_result) {
(ComprehensionMode::Set, Value::Set(set)) => {
let mut new_set = set.as_ref().clone();
new_set.insert(value_to_add);
Value::Set(crate::Rc::new(new_set))
}
(ComprehensionMode::Array, Value::Array(arr)) => {
let mut new_arr = arr.as_ref().to_vec();
new_arr.push(value_to_add);
Value::Array(crate::Rc::new(new_arr))
}
(ComprehensionMode::Object, Value::Object(obj)) => {
if let Some(key) = key_value {
let mut new_obj = obj.as_ref().clone();
new_obj.insert(key, value_to_add);
Value::Object(crate::Rc::new(new_obj))
} else {
self.comprehension_stack.push(comprehension_context);
return Err(VmError::InvalidIteration {
value: Value::String(Arc::from("Object comprehension requires key")),
});
}
}
(_mode, other) => {
self.comprehension_stack.push(comprehension_context);
return Err(VmError::InvalidIteration { value: other });
}
};
self.registers[result_reg] = updated_result;
if let Some(iter_state) = comprehension_context.iteration_state.as_mut() {
match iter_state {
IterationState::Object { current_key, .. } => {
let tracked_key =
if comprehension_context.key_reg != comprehension_context.value_reg {
self.registers[comprehension_context.key_reg as usize].clone()
} else {
self.registers[comprehension_context.value_reg as usize].clone()
};
*current_key = Some(tracked_key);
}
IterationState::Set { current_item, .. } => {
*current_item =
Some(self.registers[comprehension_context.value_reg as usize].clone());
}
IterationState::Array { .. } => {}
}
iter_state.advance();
let has_next = self.setup_next_iteration(
iter_state,
comprehension_context.key_reg,
comprehension_context.value_reg,
)?;
if has_next {
self.pc = comprehension_context.body_start as usize - 1;
} else {
comprehension_context.iteration_state = None;
self.pc = comprehension_context.comprehension_end as usize - 1;
}
}
self.comprehension_stack.push(comprehension_context);
Ok(())
}
fn execute_comprehension_yield_suspendable(
&mut self,
value_reg: u8,
key_reg: Option<u8>,
) -> Result<()> {
let comprehension_index = (0..self.execution_stack.len())
.rev()
.find(|&idx| {
self.execution_stack
.get(idx)
.is_some_and(|frame| matches!(frame.kind, FrameKind::Comprehension { .. }))
})
.ok_or(VmError::InvalidIteration {
value: Value::String(Arc::from("No active comprehension")),
})?;
let (iteration_state_snapshot, key_reg_idx, value_reg_idx, body_start, comprehension_end) = {
let frame = self.execution_stack.get_mut(comprehension_index).ok_or(
VmError::InvalidIteration {
value: Value::String(Arc::from("No active comprehension")),
},
)?;
match &mut frame.kind {
FrameKind::Comprehension { context, .. } => {
let value_to_add = self.registers[value_reg as usize].clone();
let key_value = if let Some(key_reg) = key_reg {
Some(self.registers[key_reg as usize].clone())
} else if matches!(context.mode, ComprehensionMode::Object) {
Some(self.registers[context.key_reg as usize].clone())
} else {
None
};
let result_reg_idx = context.result_reg as usize;
let current_result = self.registers[result_reg_idx].clone();
let mode = context.mode.clone();
let updated_result = match (mode, current_result) {
(ComprehensionMode::Set, Value::Set(set)) => {
let mut new_set = set.as_ref().clone();
new_set.insert(value_to_add);
Value::Set(crate::Rc::new(new_set))
}
(ComprehensionMode::Array, Value::Array(arr)) => {
let mut new_arr = arr.as_ref().to_vec();
new_arr.push(value_to_add);
Value::Array(crate::Rc::new(new_arr))
}
(ComprehensionMode::Object, Value::Object(obj)) => {
if let Some(key) = key_value {
let mut new_obj = obj.as_ref().clone();
new_obj.insert(key, value_to_add);
Value::Object(crate::Rc::new(new_obj))
} else {
return Err(VmError::InvalidIteration {
value: Value::String(Arc::from(
"Object comprehension requires key",
)),
});
}
}
(_mode, other) => {
return Err(VmError::InvalidIteration { value: other });
}
};
self.registers[result_reg_idx] = updated_result;
if let Some(iter_state) = context.iteration_state.as_mut() {
match iter_state {
IterationState::Object { current_key, .. } => {
let tracked_key = if context.key_reg != context.value_reg {
self.registers[context.key_reg as usize].clone()
} else {
self.registers[context.value_reg as usize].clone()
};
*current_key = Some(tracked_key);
}
IterationState::Set { current_item, .. } => {
*current_item =
Some(self.registers[context.value_reg as usize].clone());
}
IterationState::Array { .. } => {}
}
iter_state.advance();
}
(
context.iteration_state.clone(),
context.key_reg,
context.value_reg,
context.body_start,
context.comprehension_end,
)
}
_ => {
return Err(VmError::InvalidIteration {
value: Value::String(Arc::from("No active comprehension")),
});
}
}
};
if let Some(state) = iteration_state_snapshot.as_ref() {
let has_next = self.setup_next_iteration(state, key_reg_idx, value_reg_idx)?;
if has_next {
if let Some(frame) = self.execution_stack.get_mut(comprehension_index) {
frame.pc = body_start as usize;
self.frame_pc_overridden = true;
}
} else if let Some(frame) = self.execution_stack.get_mut(comprehension_index) {
if let FrameKind::Comprehension { context, .. } = &mut frame.kind {
context.iteration_state = None;
}
frame.pc = comprehension_end as usize;
self.frame_pc_overridden = true;
}
}
Ok(())
}
pub(super) fn execute_comprehension_end(&mut self) -> Result<()> {
match self.execution_mode {
ExecutionMode::RunToCompletion => self.execute_comprehension_end_run_to_completion(),
ExecutionMode::Suspendable => self.execute_comprehension_end_suspendable(),
}
}
fn execute_comprehension_end_run_to_completion(&mut self) -> Result<()> {
if let Some(_context) = self.comprehension_stack.pop() {
Ok(())
} else {
Err(VmError::InvalidIteration {
value: Value::String(Arc::from("No active comprehension context")),
})
}
}
fn execute_comprehension_end_suspendable(&mut self) -> Result<()> {
let mut unwound_frames: Vec<ExecutionFrame> = Vec::new();
loop {
let frame = match self.execution_stack.pop() {
Some(frame) => frame,
None => {
// Restore any frames we already unwound before propagating the error.
while let Some(restored) = unwound_frames.pop() {
self.execution_stack.push(restored);
}
return Err(VmError::InvalidIteration {
value: Value::String(Arc::from("No active comprehension context")),
});
}
};
let ExecutionFrame {
pc: frame_pc,
kind: frame_kind,
} = frame;
match frame_kind {
FrameKind::Comprehension {
return_pc: _,
context,
} => {
let raw_target = context.resume_pc;
let resume_pc = if raw_target <= self.pc {
self.pc.saturating_add(1)
} else if raw_target == self.pc.saturating_add(1) {
raw_target
} else {
raw_target.saturating_sub(1)
};
if let Some(parent) = self.execution_stack.last_mut() {
parent.pc = resume_pc;
}
while let Some(restored) = unwound_frames.pop() {
self.execution_stack.push(restored);
}
return Ok(());
}
FrameKind::Loop { return_pc, context } => {
if let Some(parent) = self.execution_stack.last_mut() {
parent.pc = return_pc;
}
// Keep the loop frame available so we can restore it if we discover a mismatch.
unwound_frames.push(ExecutionFrame::new(
frame_pc,
FrameKind::Loop { return_pc, context },
));
}
other_kind => {
let message = format!(
"Mismatched comprehension frame: frame={:?} stack_depth={} unwound_loops={}",
&other_kind,
self.execution_stack.len(),
unwound_frames.len()
);
// Put the unexpected frame back on the stack along with any loops we unwound.
self.execution_stack
.push(ExecutionFrame::new(frame_pc, other_kind));
while let Some(restored) = unwound_frames.pop() {
self.execution_stack.push(restored);
}
return Err(VmError::InvalidIteration {
value: Value::String(Arc::from(message.into_boxed_str())),
});
}
}
}
}
}

97
src/rvm/vm/context.rs Normal file
View File

@@ -0,0 +1,97 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::rvm::instructions::{ComprehensionMode, LoopMode};
use crate::value::Value;
use crate::Rc;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::vec::Vec;
/// Loop execution context for managing iteration state
#[derive(Debug, Clone)]
pub struct LoopContext {
pub mode: LoopMode,
pub iteration_state: IterationState,
pub key_reg: u8,
pub value_reg: u8,
pub result_reg: u8,
pub body_start: u16,
pub loop_end: u16,
pub loop_next_pc: u16, // PC of the LoopNext instruction to avoid searching
pub body_resume_pc: usize,
pub success_count: usize,
pub total_iterations: usize,
pub current_iteration_failed: bool, // Track if current iteration had condition failures
}
/// Iterator state for different collection types
#[derive(Debug, Clone)]
pub enum IterationState {
Array {
items: Rc<Vec<Value>>,
index: usize,
},
Object {
obj: Rc<BTreeMap<Value, Value>>,
current_key: Option<Value>,
first_iteration: bool,
},
Set {
items: Rc<BTreeSet<Value>>,
current_item: Option<Value>,
first_iteration: bool,
},
}
impl IterationState {
pub(super) fn advance(&mut self) {
match self {
IterationState::Array { index, .. } => {
*index += 1;
}
IterationState::Object {
first_iteration, ..
} => {
*first_iteration = false;
}
IterationState::Set {
first_iteration, ..
} => {
*first_iteration = false;
}
}
}
}
#[allow(unused)]
#[derive(Debug, Clone)]
pub struct CallRuleContext {
pub return_pc: usize,
pub dest_reg: u8,
pub result_reg: u8,
pub rule_index: u16,
pub rule_type: crate::rvm::program::RuleType,
pub current_definition_index: usize,
pub current_body_index: usize,
}
/// Context for tracking active comprehensions
#[derive(Debug, Clone)]
pub(super) struct ComprehensionContext {
/// Type of comprehension (Array, Set, Object)
pub(super) mode: ComprehensionMode,
/// Register storing the comprehension result collection
pub(super) result_reg: u8,
/// Register holding the current iteration key
pub(super) key_reg: u8,
/// Register holding the current iteration value
pub(super) value_reg: u8,
/// Jump target for comprehension body start
pub(super) body_start: u16,
/// Jump target for comprehension end
pub(super) comprehension_end: u16,
/// Iteration state when comprehension manages iteration itself (None when driven by LoopStart/LoopNext)
pub(super) iteration_state: Option<IterationState>,
/// Resume location for the parent frame once this comprehension completes
pub(super) resume_pc: usize,
}

771
src/rvm/vm/dispatch.rs Normal file
View File

@@ -0,0 +1,771 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::rvm::instructions::{Instruction, LiteralOrRegister};
use crate::rvm::program::Program;
use crate::value::Value;
use alloc::collections::BTreeSet;
use alloc::vec::Vec;
use core::mem;
use super::errors::{Result, VmError};
use super::execution_model::{ExecutionMode, SuspendReason};
use super::loops::LoopParams;
use super::machine::RegoVM;
pub(super) enum InstructionOutcome {
Continue,
Return(Value),
Break,
Suspend { reason: SuspendReason },
}
impl RegoVM {
pub(super) fn execute_instruction(
&mut self,
program: &Program,
instruction: Instruction,
) -> Result<InstructionOutcome> {
self.execute_load_and_move(program, instruction)
}
fn execute_load_and_move(
&mut self,
program: &Program,
instruction: Instruction,
) -> Result<InstructionOutcome> {
use Instruction::*;
match instruction {
Load { dest, literal_idx } => {
if let Some(value) = program.literals.get(literal_idx as usize) {
self.registers[dest as usize] = value.clone();
Ok(InstructionOutcome::Continue)
} else {
Err(VmError::LiteralIndexOutOfBounds {
index: literal_idx as usize,
})
}
}
LoadTrue { dest } => {
self.registers[dest as usize] = Value::Bool(true);
Ok(InstructionOutcome::Continue)
}
LoadFalse { dest } => {
self.registers[dest as usize] = Value::Bool(false);
Ok(InstructionOutcome::Continue)
}
LoadNull { dest } => {
self.registers[dest as usize] = Value::Null;
Ok(InstructionOutcome::Continue)
}
LoadBool { dest, value } => {
self.registers[dest as usize] = Value::Bool(value);
Ok(InstructionOutcome::Continue)
}
LoadData { dest } => {
self.registers[dest as usize] = self.data.clone();
Ok(InstructionOutcome::Continue)
}
LoadInput { dest } => {
self.registers[dest as usize] = self.input.clone();
Ok(InstructionOutcome::Continue)
}
Move { dest, src } => {
self.registers[dest as usize] = self.registers[src as usize].clone();
Ok(InstructionOutcome::Continue)
}
other => self.execute_arithmetic_instruction(program, other),
}
}
fn execute_arithmetic_instruction(
&mut self,
_program: &Program,
instruction: Instruction,
) -> Result<InstructionOutcome> {
use Instruction::*;
match instruction {
Add { dest, left, right } => {
let a = &self.registers[left as usize];
let b = &self.registers[right as usize];
if a == &Value::Undefined || b == &Value::Undefined {
self.registers[dest as usize] = Value::Undefined;
return Ok(InstructionOutcome::Continue);
}
let result = self.add_values(a, b)?;
self.registers[dest as usize] = result;
Ok(InstructionOutcome::Continue)
}
Sub { dest, left, right } => {
let a = &self.registers[left as usize];
let b = &self.registers[right as usize];
if a == &Value::Undefined || b == &Value::Undefined {
self.registers[dest as usize] = Value::Undefined;
return Ok(InstructionOutcome::Continue);
}
let result = self.sub_values(a, b)?;
self.registers[dest as usize] = result;
Ok(InstructionOutcome::Continue)
}
Mul { dest, left, right } => {
let a = &self.registers[left as usize];
let b = &self.registers[right as usize];
if a == &Value::Undefined || b == &Value::Undefined {
self.registers[dest as usize] = Value::Undefined;
return Ok(InstructionOutcome::Continue);
}
let result = self.mul_values(a, b)?;
self.registers[dest as usize] = result;
Ok(InstructionOutcome::Continue)
}
Div { dest, left, right } => {
let a = &self.registers[left as usize];
let b = &self.registers[right as usize];
if a == &Value::Undefined || b == &Value::Undefined {
self.registers[dest as usize] = Value::Undefined;
return Ok(InstructionOutcome::Continue);
}
let result = self.div_values(a, b)?;
self.registers[dest as usize] = result;
Ok(InstructionOutcome::Continue)
}
Mod { dest, left, right } => {
let a = &self.registers[left as usize];
let b = &self.registers[right as usize];
if a == &Value::Undefined || b == &Value::Undefined {
self.registers[dest as usize] = Value::Undefined;
return Ok(InstructionOutcome::Continue);
}
let result = self.mod_values(a, b)?;
self.registers[dest as usize] = result;
Ok(InstructionOutcome::Continue)
}
other => self.execute_comparison_instruction(_program, other),
}
}
fn execute_comparison_instruction(
&mut self,
_program: &Program,
instruction: Instruction,
) -> Result<InstructionOutcome> {
use Instruction::*;
match instruction {
Eq { dest, left, right } => {
let a = &self.registers[left as usize];
let b = &self.registers[right as usize];
if a == &Value::Undefined || b == &Value::Undefined {
self.registers[dest as usize] = Value::Undefined;
return Ok(InstructionOutcome::Continue);
}
self.registers[dest as usize] = Value::Bool(a == b);
Ok(InstructionOutcome::Continue)
}
Ne { dest, left, right } => {
let a = &self.registers[left as usize];
let b = &self.registers[right as usize];
if a == &Value::Undefined || b == &Value::Undefined {
self.registers[dest as usize] = Value::Undefined;
return Ok(InstructionOutcome::Continue);
}
self.registers[dest as usize] = Value::Bool(a != b);
Ok(InstructionOutcome::Continue)
}
Lt { dest, left, right } => {
let a = &self.registers[left as usize];
let b = &self.registers[right as usize];
if a == &Value::Undefined || b == &Value::Undefined {
self.registers[dest as usize] = Value::Undefined;
return Ok(InstructionOutcome::Continue);
}
if self.strict_builtin_errors && mem::discriminant(a) != mem::discriminant(b) {
return Err(VmError::ArithmeticError(alloc::format!(
"#undefined: cannot compare values of different types (left={a:?}, right={b:?})"
)));
}
self.registers[dest as usize] = Value::Bool(a < b);
Ok(InstructionOutcome::Continue)
}
Le { dest, left, right } => {
let a = &self.registers[left as usize];
let b = &self.registers[right as usize];
if a == &Value::Undefined || b == &Value::Undefined {
self.registers[dest as usize] = Value::Undefined;
return Ok(InstructionOutcome::Continue);
}
if self.strict_builtin_errors && mem::discriminant(a) != mem::discriminant(b) {
return Err(VmError::ArithmeticError(alloc::format!(
"#undefined: cannot compare values of different types (left={a:?}, right={b:?})"
)));
}
self.registers[dest as usize] = Value::Bool(a <= b);
Ok(InstructionOutcome::Continue)
}
Gt { dest, left, right } => {
let a = &self.registers[left as usize];
let b = &self.registers[right as usize];
if a == &Value::Undefined || b == &Value::Undefined {
self.registers[dest as usize] = Value::Undefined;
return Ok(InstructionOutcome::Continue);
}
if self.strict_builtin_errors && mem::discriminant(a) != mem::discriminant(b) {
return Err(VmError::ArithmeticError(alloc::format!(
"#undefined: cannot compare values of different types (left={a:?}, right={b:?})"
)));
}
self.registers[dest as usize] = Value::Bool(a > b);
Ok(InstructionOutcome::Continue)
}
Ge { dest, left, right } => {
let a = &self.registers[left as usize];
let b = &self.registers[right as usize];
if a == &Value::Undefined || b == &Value::Undefined {
self.registers[dest as usize] = Value::Undefined;
return Ok(InstructionOutcome::Continue);
}
if self.strict_builtin_errors && mem::discriminant(a) != mem::discriminant(b) {
return Err(VmError::ArithmeticError(alloc::format!(
"#undefined: cannot compare values of different types (left={a:?}, right={b:?})"
)));
}
self.registers[dest as usize] = Value::Bool(a >= b);
Ok(InstructionOutcome::Continue)
}
And { dest, left, right } => {
let left_value = &self.registers[left as usize];
let right_value = &self.registers[right as usize];
if left_value == &Value::Undefined || right_value == &Value::Undefined {
self.registers[dest as usize] = Value::Undefined;
return Ok(InstructionOutcome::Continue);
}
match (self.to_bool(left_value), self.to_bool(right_value)) {
(Some(a), Some(b)) => {
self.registers[dest as usize] = Value::Bool(a && b);
Ok(InstructionOutcome::Continue)
}
_ => Err(VmError::ArithmeticError(alloc::format!(
"#undefined: logical AND expects booleans (left={left_value:?}, right={right_value:?})"
))),
}
}
Or { dest, left, right } => {
let left_value = &self.registers[left as usize];
let right_value = &self.registers[right as usize];
if left_value == &Value::Undefined || right_value == &Value::Undefined {
self.registers[dest as usize] = Value::Undefined;
return Ok(InstructionOutcome::Continue);
}
match (self.to_bool(left_value), self.to_bool(right_value)) {
(Some(a), Some(b)) => {
self.registers[dest as usize] = Value::Bool(a || b);
Ok(InstructionOutcome::Continue)
}
_ => Err(VmError::ArithmeticError(alloc::format!(
"#undefined: logical OR expects booleans (left={left_value:?}, right={right_value:?})"
))),
}
}
Not { dest, operand } => {
let operand_value = &self.registers[operand as usize];
if operand_value == &Value::Undefined {
self.registers[dest as usize] = Value::Undefined;
return Ok(InstructionOutcome::Continue);
}
if let Some(value) = self.to_bool(operand_value) {
self.registers[dest as usize] = Value::Bool(!value);
Ok(InstructionOutcome::Continue)
} else {
Err(VmError::ArithmeticError(alloc::format!(
"#undefined: logical NOT expects a boolean (operand={operand_value:?})"
)))
}
}
AssertCondition { condition } => {
let value = &self.registers[condition as usize];
let condition_result = match value {
Value::Bool(b) => *b,
Value::Undefined => false,
_ => true,
};
self.handle_condition(condition_result)?;
Ok(InstructionOutcome::Continue)
}
AssertNotUndefined { register } => {
let value = &self.registers[register as usize];
let is_undefined = matches!(value, Value::Undefined);
self.handle_condition(!is_undefined)?;
Ok(InstructionOutcome::Continue)
}
other => self.execute_call_instruction(_program, other),
}
}
fn execute_call_instruction(
&mut self,
_program: &Program,
instruction: Instruction,
) -> Result<InstructionOutcome> {
use Instruction::*;
match instruction {
BuiltinCall { params_index } => {
self.execute_builtin_call(params_index)?;
Ok(InstructionOutcome::Continue)
}
HostAwait { dest, arg, id } => {
let argument = self.registers[arg as usize].clone();
let identifier = self
.registers
.get(id as usize)
.cloned()
.unwrap_or(Value::Undefined);
match self.execution_mode {
ExecutionMode::RunToCompletion => {
let response = self.next_host_await_response(&identifier, dest)?;
if self.registers.len() <= dest as usize {
self.registers.resize(dest as usize + 1, Value::Undefined);
}
self.registers[dest as usize] = response;
Ok(InstructionOutcome::Continue)
}
ExecutionMode::Suspendable => Ok(InstructionOutcome::Suspend {
reason: SuspendReason::HostAwait {
dest,
argument,
identifier,
},
}),
}
}
FunctionCall { params_index } => {
self.execute_function_call(params_index)?;
Ok(InstructionOutcome::Continue)
}
Return { value } => {
let result = self.registers[value as usize].clone();
Ok(InstructionOutcome::Return(result))
}
CallRule { dest, rule_index } => {
self.execute_call_rule(dest, rule_index)?;
Ok(InstructionOutcome::Continue)
}
RuleInit {
result_reg,
rule_index,
} => {
self.execute_rule_init(result_reg, rule_index)?;
Ok(InstructionOutcome::Continue)
}
DestructuringSuccess {} => Ok(InstructionOutcome::Break),
RuleReturn {} => {
self.execute_rule_return()?;
Ok(InstructionOutcome::Break)
}
other => self.execute_collection_instruction(_program, other),
}
}
fn execute_collection_instruction(
&mut self,
program: &Program,
instruction: Instruction,
) -> Result<InstructionOutcome> {
use Instruction::*;
match instruction {
ObjectSet { obj, key, value } => {
let key_value = self.registers[key as usize].clone();
let value_value = self.registers[value as usize].clone();
let mut obj_value = mem::replace(&mut self.registers[obj as usize], Value::Null);
if let Ok(obj_mut) = obj_value.as_object_mut() {
obj_mut.insert(key_value, value_value);
self.registers[obj as usize] = obj_value;
} else {
self.registers[obj as usize] = obj_value;
return Err(VmError::RegisterNotObject { register: obj });
}
Ok(InstructionOutcome::Continue)
}
ObjectCreate { params_index } => {
let params = program
.instruction_data
.get_object_create_params(params_index)
.ok_or(VmError::InvalidObjectCreateParams {
index: params_index,
})?;
let mut any_undefined = false;
for &(_, value_reg) in params.literal_key_field_pairs() {
if matches!(self.registers[value_reg as usize], Value::Undefined) {
any_undefined = true;
break;
}
}
if !any_undefined {
for &(key_reg, value_reg) in params.field_pairs() {
if matches!(self.registers[key_reg as usize], Value::Undefined)
|| matches!(self.registers[value_reg as usize], Value::Undefined)
{
any_undefined = true;
break;
}
}
}
if any_undefined {
self.registers[params.dest as usize] = Value::Undefined;
} else {
let mut obj_value = program
.literals
.get(params.template_literal_idx as usize)
.ok_or(VmError::InvalidTemplateLiteralIndex {
index: params.template_literal_idx,
})?
.clone();
if let Ok(obj_mut) = obj_value.as_object_mut() {
let mut literal_updates = params.literal_key_field_pairs().iter();
let mut current_literal_update = literal_updates.next();
for (key, value) in obj_mut.iter_mut() {
if let Some(&(literal_idx, value_reg)) = current_literal_update {
if let Some(literal_key) =
program.literals.get(literal_idx as usize)
{
if key == literal_key {
*value = self.registers[value_reg as usize].clone();
current_literal_update = literal_updates.next();
}
}
} else {
break;
}
}
while let Some(&(literal_idx, value_reg)) = current_literal_update {
if let Some(key_value) = program.literals.get(literal_idx as usize) {
let value_value = self.registers[value_reg as usize].clone();
obj_mut.insert(key_value.clone(), value_value);
}
current_literal_update = literal_updates.next();
}
for &(key_reg, value_reg) in params.field_pairs() {
let key_value = self.registers[key_reg as usize].clone();
let value_value = self.registers[value_reg as usize].clone();
obj_mut.insert(key_value, value_value);
}
} else {
return Err(VmError::ObjectCreateInvalidTemplate);
}
self.registers[params.dest as usize] = obj_value;
}
Ok(InstructionOutcome::Continue)
}
Index {
dest,
container,
key,
} => {
let key_value = &self.registers[key as usize];
let container_value = &self.registers[container as usize];
let result = container_value[key_value].clone();
self.registers[dest as usize] = result;
Ok(InstructionOutcome::Continue)
}
IndexLiteral {
dest,
container,
literal_idx,
} => {
let container_value = &self.registers[container as usize];
if let Some(key_value) = program.literals.get(literal_idx as usize) {
let result = container_value[key_value].clone();
self.registers[dest as usize] = result;
Ok(InstructionOutcome::Continue)
} else {
Err(VmError::LiteralIndexOutOfBounds {
index: literal_idx as usize,
})
}
}
ArrayNew { dest } => {
let empty_array = Value::Array(crate::Rc::new(Vec::new()));
self.registers[dest as usize] = empty_array;
Ok(InstructionOutcome::Continue)
}
ArrayPush { arr, value } => {
let value_to_push = self.registers[value as usize].clone();
let mut arr_value = mem::replace(&mut self.registers[arr as usize], Value::Null);
if let Ok(arr_mut) = arr_value.as_array_mut() {
arr_mut.push(value_to_push);
self.registers[arr as usize] = arr_value;
} else {
self.registers[arr as usize] = arr_value;
return Err(VmError::RegisterNotArray { register: arr });
}
Ok(InstructionOutcome::Continue)
}
ArrayCreate { params_index } => {
if let Some(params) = program
.instruction_data
.get_array_create_params(params_index)
{
let mut any_undefined = false;
for &reg in params.element_registers() {
if matches!(self.registers[reg as usize], Value::Undefined) {
any_undefined = true;
break;
}
}
if any_undefined {
self.registers[params.dest as usize] = Value::Undefined;
} else {
let elements: Vec<Value> = params
.element_registers()
.iter()
.map(|&reg| self.registers[reg as usize].clone())
.collect();
let array_value = Value::Array(crate::Rc::new(elements));
self.registers[params.dest as usize] = array_value;
}
Ok(InstructionOutcome::Continue)
} else {
Err(VmError::InvalidArrayCreateParams {
index: params_index,
})
}
}
SetNew { dest } => {
let empty_set = Value::Set(crate::Rc::new(BTreeSet::new()));
self.registers[dest as usize] = empty_set;
Ok(InstructionOutcome::Continue)
}
SetAdd { set, value } => {
let value_to_add = self.registers[value as usize].clone();
let mut set_value = mem::replace(&mut self.registers[set as usize], Value::Null);
if let Ok(set_mut) = set_value.as_set_mut() {
set_mut.insert(value_to_add);
self.registers[set as usize] = set_value;
} else {
self.registers[set as usize] = set_value;
return Err(VmError::RegisterNotSet { register: set });
}
Ok(InstructionOutcome::Continue)
}
SetCreate { params_index } => {
if let Some(params) = program.instruction_data.get_set_create_params(params_index) {
let mut any_undefined = false;
for &reg in params.element_registers() {
if matches!(self.registers[reg as usize], Value::Undefined) {
any_undefined = true;
break;
}
}
if any_undefined {
self.registers[params.dest as usize] = Value::Undefined;
} else {
let mut set = BTreeSet::new();
for &reg in params.element_registers() {
set.insert(self.registers[reg as usize].clone());
}
let set_value = Value::Set(crate::Rc::new(set));
self.registers[params.dest as usize] = set_value;
}
Ok(InstructionOutcome::Continue)
} else {
Err(VmError::InvalidSetCreateParams {
index: params_index,
})
}
}
Contains {
dest,
collection,
value,
} => {
let value_to_check = &self.registers[value as usize];
let collection_value = &self.registers[collection as usize];
let result = match collection_value {
Value::Set(set_elements) => Value::Bool(set_elements.contains(value_to_check)),
Value::Array(array_items) => Value::Bool(array_items.contains(value_to_check)),
Value::Object(object_fields) => Value::Bool(
object_fields.contains_key(value_to_check)
|| object_fields.values().any(|v| v == value_to_check),
),
_ => Value::Bool(false),
};
self.registers[dest as usize] = result;
Ok(InstructionOutcome::Continue)
}
Count { dest, collection } => {
let collection_value = &self.registers[collection as usize];
let result = match collection_value {
Value::Array(array_items) => Value::from(array_items.len()),
Value::Object(object_fields) => Value::from(object_fields.len()),
Value::Set(set_elements) => Value::from(set_elements.len()),
_ => Value::Undefined,
};
self.registers[dest as usize] = result;
Ok(InstructionOutcome::Continue)
}
other => self.execute_loop_instruction(program, other),
}
}
fn execute_loop_instruction(
&mut self,
program: &Program,
instruction: Instruction,
) -> Result<InstructionOutcome> {
use Instruction::*;
match instruction {
LoopStart { params_index } => {
let loop_params = &self.program.instruction_data.loop_params[params_index as usize];
let mode = loop_params.mode.clone();
let params = LoopParams {
collection: loop_params.collection,
key_reg: loop_params.key_reg,
value_reg: loop_params.value_reg,
result_reg: loop_params.result_reg,
body_start: loop_params.body_start,
loop_end: loop_params.loop_end,
};
self.execute_loop_start(&mode, params)?;
Ok(InstructionOutcome::Continue)
}
LoopNext {
body_start,
loop_end,
} => {
self.execute_loop_next(body_start, loop_end)?;
Ok(InstructionOutcome::Continue)
}
Halt {} => {
let result = self.registers[0].clone();
Ok(InstructionOutcome::Return(result))
}
other => self.execute_virtual_instruction(program, other),
}
}
fn execute_virtual_instruction(
&mut self,
program: &Program,
instruction: Instruction,
) -> Result<InstructionOutcome> {
use Instruction::*;
match instruction {
ChainedIndex { params_index } => {
let params = program
.instruction_data
.get_chained_index_params(params_index)
.ok_or(VmError::InvalidChainedIndexParams {
index: params_index,
})?;
let mut current_value = self.registers[params.root as usize].clone();
for component in &params.path_components {
let key_value = match component {
LiteralOrRegister::Literal(idx) => program
.literals
.get(*idx as usize)
.ok_or(VmError::LiteralIndexOutOfBounds {
index: *idx as usize,
})?
.clone(),
LiteralOrRegister::Register(reg) => self.registers[*reg as usize].clone(),
};
current_value = current_value[&key_value].clone();
if current_value == Value::Undefined {
break;
}
}
self.registers[params.dest as usize] = current_value;
Ok(InstructionOutcome::Continue)
}
VirtualDataDocumentLookup { params_index } => {
self.execute_virtual_data_document_lookup(params_index)?;
Ok(InstructionOutcome::Continue)
}
ComprehensionBegin { params_index } => {
let params = program
.instruction_data
.get_comprehension_begin_params(params_index)
.ok_or(VmError::InvalidComprehensionBeginParams {
index: params_index,
})?
.clone();
self.execute_comprehension_begin(&params)?;
Ok(InstructionOutcome::Continue)
}
ComprehensionYield { value_reg, key_reg } => {
self.execute_comprehension_yield(value_reg, key_reg)?;
Ok(InstructionOutcome::Continue)
}
ComprehensionEnd {} => {
self.execute_comprehension_end()?;
Ok(InstructionOutcome::Continue)
}
unexpected => Err(VmError::Internal(alloc::format!(
"Unhandled instruction variant: {:?}",
unexpected
))),
}
}
}

121
src/rvm/vm/errors.rs Normal file
View File

@@ -0,0 +1,121 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::value::Value;
use alloc::string::String;
use alloc::vec::Vec;
use thiserror::Error;
/// VM execution errors
#[derive(Error, Debug, Clone, PartialEq)]
pub enum VmError {
#[error("Execution stopped: exceeded maximum instruction limit of {limit}")]
InstructionLimitExceeded { limit: usize },
#[error("Literal index {index} out of bounds")]
LiteralIndexOutOfBounds { index: usize },
#[error("Register {register} does not contain an object")]
RegisterNotObject { register: u8 },
#[error("ObjectCreate: template is not an object")]
ObjectCreateInvalidTemplate,
#[error("Register {register} does not contain an array")]
RegisterNotArray { register: u8 },
#[error("Register {register} does not contain a set")]
RegisterNotSet { register: u8 },
#[error("Rule index {index} out of bounds")]
RuleIndexOutOfBounds { index: u16 },
#[error("Rule index {index} has no info")]
RuleInfoMissing { index: u16 },
#[error("Invalid object create params index: {index}")]
InvalidObjectCreateParams { index: u16 },
#[error("Invalid template literal index: {index}")]
InvalidTemplateLiteralIndex { index: u16 },
#[error("Invalid chained index params index: {index}")]
InvalidChainedIndexParams { index: u16 },
#[error("Invalid array create params index: {index}")]
InvalidArrayCreateParams { index: u16 },
#[error("Invalid set create params index: {index}")]
InvalidSetCreateParams { index: u16 },
#[error("Invalid virtual data document lookup params index: {index}")]
InvalidVirtualDataDocumentLookupParams { index: u16 },
#[error("Invalid comprehension start params index: {index}")]
InvalidComprehensionBeginParams { index: u16 },
#[error("Invalid rule index: {rule_index:?}")]
InvalidRuleIndex { rule_index: Value },
#[error("Invalid rule tree entry: {value:?}")]
InvalidRuleTreeEntry { value: Value },
#[error("Builtin function expects exactly {expected} arguments, got {actual}")]
BuiltinArgumentMismatch { expected: u16, actual: usize },
#[error("Builtin function not resolved: {name}")]
BuiltinNotResolved { name: String },
#[error("Cannot add {left:?} and {right:?}")]
InvalidAddition { left: Value, right: Value },
#[error("Cannot subtract {left:?} and {right:?}")]
InvalidSubtraction { left: Value, right: Value },
#[error("Cannot multiply {left:?} and {right:?}")]
InvalidMultiplication { left: Value, right: Value },
#[error("Cannot divide {left:?} and {right:?}")]
InvalidDivision { left: Value, right: Value },
#[error("modulo on floating-point number")]
ModuloOnFloat,
#[error("Cannot modulo {left:?} and {right:?}")]
InvalidModulo { left: Value, right: Value },
#[error("Cannot iterate over {value:?}")]
InvalidIteration { value: Value },
#[error("HostAwait executed but no response provided for destination register {dest} (id: {identifier:?})")]
HostAwaitResponseMissing { dest: u8, identifier: Value },
#[error("Assertion failed")]
AssertionFailed,
#[error("Rule-data conflict: {0}")]
RuleDataConflict(String),
#[error("Arithmetic error: {0}")]
ArithmeticError(String),
#[error("Entry point index {index} out of bounds (max: {max_index})")]
InvalidEntryPointIndex { index: usize, max_index: usize },
#[error("Entry point '{name}' not found. Available entry points: {available:?}")]
EntryPointNotFound {
name: String,
available: Vec<String>,
},
#[error("Internal VM error: {0}")]
Internal(String),
}
impl From<anyhow::Error> for VmError {
fn from(err: anyhow::Error) -> Self {
VmError::ArithmeticError(alloc::format!("{}", err))
}
}
pub type Result<T> = core::result::Result<T, VmError>;

591
src/rvm/vm/execution.rs Normal file
View File

@@ -0,0 +1,591 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::rvm::instructions::Instruction;
use crate::rvm::program::Program;
use crate::value::Value;
use alloc::string::String;
use alloc::vec::Vec;
use super::dispatch::InstructionOutcome;
use super::errors::{Result, VmError};
use super::execution_model::{
ExecutionFrame, ExecutionMode, ExecutionState, FrameKind, RuleFrameData, RuleFramePhase,
SuspendReason,
};
use super::machine::RegoVM;
impl RegoVM {
pub fn execute(&mut self) -> Result<Value> {
match self.execution_mode {
ExecutionMode::RunToCompletion => self.execute_run_to_completion(),
ExecutionMode::Suspendable => self.execute_suspendable(),
}
}
pub fn execute_entry_point_by_index(&mut self, index: usize) -> Result<Value> {
let entry_points: Vec<(String, usize)> = self
.program
.entry_points
.iter()
.map(|(name, pc)| (name.clone(), *pc))
.collect();
if index >= entry_points.len() {
return Err(VmError::InvalidEntryPointIndex {
index,
max_index: entry_points.len().saturating_sub(1),
});
}
let (_entry_point_name, entry_point_pc) = &entry_points[index];
if *entry_point_pc >= self.program.instructions.len() {
return Err(VmError::Internal(alloc::format!(
"Entry point PC {} >= instruction count {} for index {} | {}",
entry_point_pc,
self.program.instructions.len(),
index,
self.get_debug_state()
)));
}
match self.execution_mode {
ExecutionMode::RunToCompletion => {
self.reset_execution_state();
if let Err(e) = self.validate_vm_state() {
return Err(VmError::Internal(alloc::format!(
"VM state validation failed before entry point execution: {} | {}",
e,
self.get_debug_state()
)));
}
self.jump_to(*entry_point_pc)
}
ExecutionMode::Suspendable => {
self.reset_execution_state();
if let Err(e) = self.validate_vm_state() {
return Err(VmError::Internal(alloc::format!(
"VM state validation failed before entry point execution: {} | {}",
e,
self.get_debug_state()
)));
}
self.execute_suspendable_entry(*entry_point_pc)
}
}
}
pub fn execute_entry_point_by_name(&mut self, name: &str) -> Result<Value> {
let entry_point_pc =
self.program
.get_entry_point(name)
.ok_or_else(|| VmError::EntryPointNotFound {
name: String::from(name),
available: self.program.entry_points.keys().cloned().collect(),
})?;
if entry_point_pc >= self.program.instructions.len() {
return Err(VmError::Internal(alloc::format!(
"Entry point PC {} >= instruction count {} for '{}' | {}",
entry_point_pc,
self.program.instructions.len(),
name,
self.get_debug_state()
)));
}
match self.execution_mode {
ExecutionMode::RunToCompletion => {
self.reset_execution_state();
if let Err(e) = self.validate_vm_state() {
return Err(VmError::Internal(alloc::format!(
"VM state validation failed before entry point execution: {} | {}",
e,
self.get_debug_state()
)));
}
self.jump_to(entry_point_pc)
}
ExecutionMode::Suspendable => {
self.reset_execution_state();
if let Err(e) = self.validate_vm_state() {
return Err(VmError::Internal(alloc::format!(
"VM state validation failed before entry point execution: {} | {}",
e,
self.get_debug_state()
)));
}
self.execute_suspendable_entry(entry_point_pc)
}
}
}
pub(super) fn jump_to(&mut self, target: usize) -> Result<Value> {
let program = self.program.clone();
self.pc = target;
while self.pc < program.instructions.len() {
if self.executed_instructions >= self.max_instructions {
return Err(VmError::InstructionLimitExceeded {
limit: self.max_instructions,
});
}
self.executed_instructions += 1;
let instruction = program.instructions[self.pc].clone();
match self.execute_instruction(&program, instruction)? {
InstructionOutcome::Continue => {
self.pc += 1;
}
InstructionOutcome::Return(value) => {
return Ok(value);
}
InstructionOutcome::Break => {
return Ok(self.registers[0].clone());
}
InstructionOutcome::Suspend { reason } => {
return Err(VmError::Internal(alloc::format!(
"Suspend instruction {:?} is not supported in run-to-completion execution",
reason
)));
}
}
}
Ok(self.registers[0].clone())
}
fn execute_run_to_completion(&mut self) -> Result<Value> {
self.reset_execution_state();
self.execution_state = ExecutionState::Running;
match self.jump_to(0) {
Ok(value) => {
self.execution_state = ExecutionState::Completed {
result: value.clone(),
};
Ok(value)
}
Err(err) => {
self.execution_state = ExecutionState::Error { error: err.clone() };
Err(err)
}
}
}
fn execute_suspendable(&mut self) -> Result<Value> {
self.reset_execution_state();
self.execution_state = ExecutionState::Running;
match self.run_stackless_from(0) {
Ok(result) => Ok(result),
Err(err) => {
self.execution_state = ExecutionState::Error { error: err.clone() };
Err(err)
}
}
}
fn execute_suspendable_entry(&mut self, entry_point_pc: usize) -> Result<Value> {
self.execution_state = ExecutionState::Running;
match self.run_stackless_from(entry_point_pc) {
Ok(result) => Ok(result),
Err(err) => {
self.execution_state = ExecutionState::Error { error: err.clone() };
Err(err)
}
}
}
pub fn resume(&mut self, resume_value: Option<Value>) -> Result<Value> {
let (reason, mut last_result) = match self.execution_state.clone() {
ExecutionState::Suspended {
reason,
last_result,
..
} => (reason, last_result),
current_state => {
return Err(VmError::Internal(alloc::format!(
"Cannot resume VM when execution state is {:?}",
current_state
)));
}
};
match reason.clone() {
SuspendReason::HostAwait { dest, .. } => {
let value = resume_value.ok_or_else(|| {
VmError::Internal("HostAwait suspension requires a resume value".into())
})?;
if self.registers.len() <= dest as usize {
self.registers.resize(dest as usize + 1, Value::Undefined);
}
self.registers[dest as usize] = value;
}
other_reason => {
if resume_value.is_some() {
return Err(VmError::Internal(alloc::format!(
"Unexpected resume value supplied for {:?}",
other_reason
)));
}
}
}
self.execution_state = ExecutionState::Running;
let program = self.program.clone();
self.run_stackless_loop(&program, &mut last_result)?;
if matches!(self.execution_state, ExecutionState::Suspended { .. }) {
Ok(last_result)
} else {
self.execution_stack.clear();
self.execution_state = ExecutionState::Completed {
result: last_result.clone(),
};
Ok(last_result)
}
}
fn run_stackless_from(&mut self, start_pc: usize) -> Result<Value> {
let program = self.program.clone();
self.execution_stack.clear();
self.execution_stack.push(ExecutionFrame::main(start_pc, 0));
let mut last_result = self.registers.first().cloned().unwrap_or(Value::Undefined);
self.run_stackless_loop(&program, &mut last_result)?;
if matches!(self.execution_state, ExecutionState::Suspended { .. }) {
Ok(last_result)
} else {
self.execution_stack.clear();
self.execution_state = ExecutionState::Completed {
result: last_result.clone(),
};
Ok(last_result)
}
}
fn run_stackless_loop(&mut self, program: &Program, last_result: &mut Value) -> Result<()> {
while !self.execution_stack.is_empty() {
self.frame_pc_overridden = false;
let should_finalize_rule = if let Some(frame) = self.execution_stack.last() {
matches!(
frame.kind,
FrameKind::Rule(RuleFrameData {
phase: RuleFramePhase::Finalizing,
..
})
)
} else {
false
};
if should_finalize_rule {
let frame = self.execution_stack.pop().expect("frame available");
self.finalize_rule_execution_frame(frame, last_result)?;
if self.execution_stack.is_empty() {
break;
}
continue;
}
let frame_pc = {
let frame = self
.execution_stack
.last()
.expect("stack checked to be non-empty");
frame.pc
};
if self.execution_mode == ExecutionMode::Suspendable
&& self.breakpoints.contains(&frame_pc)
{
self.pc = frame_pc;
let snapshot = (*last_result).clone();
self.execution_state = ExecutionState::Suspended {
reason: SuspendReason::Breakpoint { pc: frame_pc },
pc: frame_pc,
last_result: snapshot,
};
return Ok(());
}
if frame_pc >= program.instructions.len() {
let frame = self.execution_stack.pop().expect("frame exists");
self.finalize_rule_execution_frame(frame, last_result)?;
if self.execution_stack.is_empty() {
break;
}
continue;
}
if self.executed_instructions >= self.max_instructions {
self.execution_state = ExecutionState::Error {
error: VmError::InstructionLimitExceeded {
limit: self.max_instructions,
},
};
return Err(VmError::InstructionLimitExceeded {
limit: self.max_instructions,
});
}
self.pc = frame_pc;
let instruction = program.instructions[self.pc].clone();
if let Some(frame_info) = self.execution_stack.last() {
if let FrameKind::Comprehension { context, .. } = &frame_info.kind {
if context.iteration_state.is_none()
&& frame_pc == context.comprehension_end as usize
&& !matches!(instruction, Instruction::ComprehensionEnd { .. })
{
let resume_pc = frame_pc;
let _completed = self.execution_stack.pop().expect("frame exists");
if let Some(parent) = self.execution_stack.last_mut() {
parent.pc = resume_pc;
self.frame_pc_overridden = true;
}
continue;
}
}
}
self.executed_instructions += 1;
let stack_depth_before = self.execution_stack.len();
match self.execute_instruction(program, instruction) {
Ok(InstructionOutcome::Continue) => {
let stack_depth_after = self.execution_stack.len();
if stack_depth_after == stack_depth_before && !self.frame_pc_overridden {
if let Some(frame) = self.execution_stack.last_mut() {
frame.pc = self.pc + 1;
}
}
if self.step_mode {
self.handle_instruction_suspend(SuspendReason::Step, &*last_result);
return Ok(());
}
}
Ok(InstructionOutcome::Return(value)) => {
self.handle_instruction_return(value, last_result)?;
if self.execution_stack.is_empty() {
break;
}
}
Ok(InstructionOutcome::Break) => {
self.handle_instruction_break(last_result)?;
if self.execution_stack.is_empty() {
break;
}
}
Ok(InstructionOutcome::Suspend { reason }) => {
self.handle_instruction_suspend(reason, last_result);
return Ok(());
}
Err(err) => {
if self.handle_instruction_error(err.clone(), last_result)? {
if self.execution_stack.is_empty() {
break;
}
continue;
} else {
self.execution_stack.clear();
return Err(err);
}
}
}
}
Ok(())
}
fn handle_instruction_suspend(&mut self, reason: SuspendReason, last_result: &Value) {
if let Some(frame) = self.execution_stack.last_mut() {
if !self.frame_pc_overridden && frame.pc <= self.pc {
frame.pc = self.pc + 1;
}
}
self.execution_state = ExecutionState::Suspended {
reason,
pc: self.pc,
last_result: last_result.clone(),
};
}
fn handle_completed_frame_kind(&mut self, kind: FrameKind, last_result: &mut Value) {
match kind {
FrameKind::Main {
return_value_register,
} => {
*last_result = self
.registers
.get(return_value_register as usize)
.cloned()
.unwrap_or(Value::Undefined);
}
FrameKind::Loop { .. } | FrameKind::Comprehension { .. } => {
*last_result = self.registers.first().cloned().unwrap_or(Value::Undefined);
}
FrameKind::Rule(_) => {
*last_result = Value::Undefined;
}
}
}
fn handle_instruction_return(&mut self, value: Value, last_result: &mut Value) -> Result<()> {
loop {
let frame = match self.execution_stack.pop() {
Some(frame) => frame,
None => {
*last_result = value;
return Ok(());
}
};
match frame.kind {
FrameKind::Rule(mut data) => {
if self.registers.len() <= data.result_reg as usize {
self.registers
.resize(data.result_reg as usize + 1, Value::Undefined);
}
self.registers[data.result_reg as usize] = value.clone();
data.accumulated_result = Some(value.clone());
data.any_body_succeeded = true;
let result = self.finalize_rule_frame_data(data)?;
*last_result = result.clone();
if let Some(parent_frame) = self.execution_stack.last_mut() {
parent_frame.pc = self.pc + 1;
}
return Ok(());
}
FrameKind::Main {
return_value_register,
} => {
if self.registers.len() <= return_value_register as usize {
self.registers
.resize(return_value_register as usize + 1, Value::Undefined);
}
self.registers[return_value_register as usize] = value.clone();
*last_result = value;
return Ok(());
}
FrameKind::Loop { return_pc, .. } | FrameKind::Comprehension { return_pc, .. } => {
if let Some(parent_frame) = self.execution_stack.last_mut() {
parent_frame.pc = return_pc;
}
// Propagate the return value outward until we reach the owning frame
continue;
}
}
}
}
fn handle_instruction_break(&mut self, last_result: &mut Value) -> Result<()> {
if let Some(frame) = self.execution_stack.pop() {
match frame.kind {
FrameKind::Rule(mut data) => {
let current_pc = frame.pc;
let next_pc = self.handle_rule_break_event(&mut data)?;
if let Some(pc) = next_pc {
self.execution_stack
.push(ExecutionFrame::new(pc, FrameKind::Rule(data)));
} else {
self.finalize_rule_execution_frame(
ExecutionFrame::new(current_pc, FrameKind::Rule(data)),
last_result,
)?;
}
}
other_kind => {
self.handle_break_non_rule(
ExecutionFrame::new(frame.pc, other_kind),
last_result,
);
}
}
}
Ok(())
}
fn handle_instruction_error(&mut self, _err: VmError, last_result: &mut Value) -> Result<bool> {
if let Some(frame) = self.execution_stack.pop() {
match frame.kind {
FrameKind::Rule(mut data) => {
let current_pc = frame.pc;
let next_pc = self.handle_rule_error_event(&mut data)?;
if let Some(pc) = next_pc {
self.execution_stack
.push(ExecutionFrame::new(pc, FrameKind::Rule(data)));
} else {
self.finalize_rule_execution_frame(
ExecutionFrame::new(current_pc, FrameKind::Rule(data)),
last_result,
)?;
}
return Ok(true);
}
other_kind => {
self.execution_stack
.push(ExecutionFrame::new(frame.pc, other_kind));
}
}
}
Ok(false)
}
fn handle_break_non_rule(&mut self, frame: ExecutionFrame, last_result: &mut Value) {
match frame.kind {
FrameKind::Main {
return_value_register,
} => {
*last_result = self
.registers
.get(return_value_register as usize)
.cloned()
.unwrap_or(Value::Undefined);
}
FrameKind::Loop { return_pc, .. } | FrameKind::Comprehension { return_pc, .. } => {
if let Some(parent_frame) = self.execution_stack.last_mut() {
parent_frame.pc = return_pc;
}
*last_result = self.registers.first().cloned().unwrap_or(Value::Undefined);
}
FrameKind::Rule(_) => {}
}
}
fn finalize_rule_execution_frame(
&mut self,
frame: ExecutionFrame,
last_result: &mut Value,
) -> Result<()> {
match frame.kind {
FrameKind::Rule(data) => {
let result = self.finalize_rule_frame_data(data)?;
*last_result = result.clone();
if let Some(parent_frame) = self.execution_stack.last_mut() {
parent_frame.pc = self.pc + 1;
}
}
other_kind => {
self.handle_completed_frame_kind(other_kind, last_result);
}
}
Ok(())
}
}

View File

@@ -0,0 +1,179 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::rvm::program::RuleType;
use crate::value::Value;
use alloc::collections::BTreeSet;
use alloc::vec::Vec;
use super::context::{ComprehensionContext, LoopContext};
use super::errors::VmError;
/// Represents a single execution context (frame) in the VM
#[derive(Debug, Clone)]
pub(super) struct ExecutionFrame {
/// Program counter for this frame
pub(super) pc: usize,
/// Frame-specific payload
pub(super) kind: FrameKind,
}
impl ExecutionFrame {
pub(super) fn new(pc: usize, kind: FrameKind) -> Self {
Self { pc, kind }
}
pub(super) fn main(pc: usize, return_register: u8) -> Self {
Self {
pc,
kind: FrameKind::Main {
return_value_register: return_register,
},
}
}
}
/// Different categories of execution frames managed by the stackless engine
#[derive(Debug, Clone)]
pub(super) enum FrameKind {
/// Main entry frame used when executing the program start point
Main { return_value_register: u8 },
/// Rule execution frame (replaces recursive jump_to calls)
Rule(RuleFrameData),
/// Loop iteration frame
Loop {
return_pc: usize,
context: LoopContext,
},
/// Comprehension frame
Comprehension {
return_pc: usize,
context: ComprehensionContext,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum RuleFramePhase {
Initializing,
ExecutingDestructuring,
ExecutingBody,
Finalizing,
}
#[derive(Debug, Clone)]
pub(super) struct RuleFrameData {
pub(super) return_pc: usize,
pub(super) dest_reg: u8,
pub(super) rule_index: u16,
pub(super) current_definition_index: usize,
pub(super) current_body_index: usize,
pub(super) total_definitions: usize,
pub(super) phase: RuleFramePhase,
pub(super) accumulated_result: Option<Value>,
pub(super) any_body_succeeded: bool,
pub(super) rule_failed_due_to_inconsistency: bool,
pub(super) rule_type: RuleType,
pub(super) result_reg: u8,
pub(super) is_function_rule: bool,
pub(super) num_registers: usize,
pub(super) num_retained_registers: usize,
pub(super) saved_registers: Vec<Value>,
pub(super) saved_loop_stack: Vec<LoopContext>,
pub(super) saved_comprehension_stack: Vec<ComprehensionContext>,
}
/// Explicit execution stack replacing the Rust call stack
#[derive(Debug, Clone, Default)]
pub(super) struct ExecutionStack {
frames: Vec<ExecutionFrame>,
}
impl ExecutionStack {
pub fn new() -> Self {
Self { frames: Vec::new() }
}
pub fn push(&mut self, frame: ExecutionFrame) {
self.frames.push(frame);
}
pub fn pop(&mut self) -> Option<ExecutionFrame> {
self.frames.pop()
}
pub fn last(&self) -> Option<&ExecutionFrame> {
self.frames.last()
}
pub fn last_mut(&mut self) -> Option<&mut ExecutionFrame> {
self.frames.last_mut()
}
pub fn is_empty(&self) -> bool {
self.frames.is_empty()
}
pub fn len(&self) -> usize {
self.frames.len()
}
pub fn get(&self, index: usize) -> Option<&ExecutionFrame> {
self.frames.get(index)
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut ExecutionFrame> {
self.frames.get_mut(index)
}
pub fn clear(&mut self) {
self.frames.clear();
}
}
/// Represents the current execution state of the VM
#[derive(Debug, Clone, PartialEq, Default)]
pub enum ExecutionState {
#[default]
Ready,
Running,
Suspended {
reason: SuspendReason,
pc: usize,
last_result: Value,
},
Completed {
result: Value,
},
Error {
error: VmError,
},
}
/// Reasons why execution may suspend
#[derive(Debug, Clone, PartialEq)]
pub enum SuspendReason {
SuspendInstruction,
Breakpoint {
pc: usize,
},
Step,
InstructionLimit,
External,
HostAwait {
dest: u8,
argument: Value,
identifier: Value,
},
}
/// Execution mode controls whether the VM runs straight through or supports suspension
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExecutionMode {
/// Execute in a single pass without exposing suspension points
RunToCompletion,
/// Execute cooperatively, allowing host-visible suspension and resume
Suspendable,
}
/// Set of breakpoints used by the suspendable engine
pub(super) type BreakpointSet = BTreeSet<usize>;

74
src/rvm/vm/functions.rs Normal file
View File

@@ -0,0 +1,74 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::value::Value;
use alloc::string::String;
use alloc::vec::Vec;
use super::errors::{Result, VmError};
use super::execution_model::ExecutionMode;
use super::machine::RegoVM;
impl RegoVM {
pub(super) fn execute_function_call(&mut self, params_index: u16) -> Result<()> {
let params =
self.program.instruction_data.function_call_params[params_index as usize].clone();
match self.execution_mode {
ExecutionMode::RunToCompletion => {
self.execute_call_rule_common(params.dest, params.func_rule_index, Some(&params))
}
ExecutionMode::Suspendable => self.execute_call_rule_suspendable(
params.dest,
params.func_rule_index,
Some(&params),
),
}
}
pub(super) fn execute_builtin_call(&mut self, params_index: u16) -> Result<()> {
let params = &self.program.instruction_data.builtin_call_params[params_index as usize];
let builtin_info = &self.program.builtin_info_table[params.builtin_index as usize];
let mut args = Vec::new();
for &arg_reg in params.arg_registers().iter() {
let arg_value = self.registers[arg_reg as usize].clone();
args.push(arg_value);
}
if (args.len() as u16) != builtin_info.num_args {
return Err(VmError::BuiltinArgumentMismatch {
expected: builtin_info.num_args,
actual: args.len(),
});
}
if let Some(builtin_fcn) = self.program.get_resolved_builtin(params.builtin_index) {
let dummy_source = crate::lexer::Source::from_contents("arg".into(), String::new())?;
let dummy_span = crate::lexer::Span {
source: dummy_source,
line: 1,
col: 1,
start: 0,
end: 3,
};
let mut dummy_exprs: Vec<crate::ast::Ref<crate::ast::Expr>> = Vec::new();
for _ in 0..args.len() {
let dummy_expr = crate::ast::Expr::Null {
span: dummy_span.clone(),
value: Value::Null,
eidx: 0,
};
dummy_exprs.push(crate::ast::Ref::new(dummy_expr));
}
let result = (builtin_fcn.0)(&dummy_span, &dummy_exprs, &args, true)?;
self.registers[params.dest as usize] = result.clone();
} else {
return Err(VmError::BuiltinNotResolved {
name: builtin_info.name.clone(),
});
}
Ok(())
}
}

661
src/rvm/vm/loops.rs Normal file
View File

@@ -0,0 +1,661 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::rvm::instructions::LoopMode;
use crate::value::Value;
use super::context::{IterationState, LoopContext};
use super::errors::{Result, VmError};
use super::execution_model::{ExecutionFrame, ExecutionMode, FrameKind};
use super::machine::RegoVM;
fn compute_body_resume_pc(loop_start_pc: usize, body_start: u16) -> usize {
if body_start == 0 {
return 0;
}
let candidate = body_start.saturating_sub(1) as usize;
if candidate == loop_start_pc {
body_start as usize
} else {
candidate
}
}
#[derive(Clone, Copy)]
pub(super) struct LoopParams {
pub(super) collection: u8,
pub(super) key_reg: u8,
pub(super) value_reg: u8,
pub(super) result_reg: u8,
pub(super) body_start: u16,
pub(super) loop_end: u16,
}
#[derive(Debug)]
enum LoopAction {
ExitWithSuccess,
ExitWithFailure,
Continue,
}
impl RegoVM {
pub(super) fn execute_loop_start(&mut self, mode: &LoopMode, params: LoopParams) -> Result<()> {
match self.execution_mode {
ExecutionMode::RunToCompletion => {
self.execute_loop_start_run_to_completion(mode, params)
}
ExecutionMode::Suspendable => self.execute_loop_start_suspendable(mode, params),
}
}
pub(super) fn execute_loop_next(&mut self, body_start: u16, loop_end: u16) -> Result<()> {
match self.execution_mode {
ExecutionMode::RunToCompletion => {
self.execute_loop_next_run_to_completion(body_start, loop_end)
}
ExecutionMode::Suspendable => self.execute_loop_next_suspendable(body_start, loop_end),
}
}
pub(super) fn handle_condition(&mut self, condition_passed: bool) -> Result<()> {
match self.execution_mode {
ExecutionMode::RunToCompletion => {
self.handle_condition_run_to_completion(condition_passed)
}
ExecutionMode::Suspendable => self.handle_condition_suspendable(condition_passed),
}
}
fn execute_loop_start_run_to_completion(
&mut self,
mode: &LoopMode,
params: LoopParams,
) -> Result<()> {
let initial_result = match mode {
LoopMode::Any | LoopMode::Every | LoopMode::ForEach => Value::Bool(false),
};
self.registers[params.result_reg as usize] = initial_result.clone();
let collection_value = self.registers[params.collection as usize].clone();
let iteration_state = match &collection_value {
Value::Array(items) => {
if items.is_empty() {
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
return Ok(());
}
IterationState::Array {
items: items.clone(),
index: 0,
}
}
Value::Object(obj) => {
if obj.is_empty() {
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
return Ok(());
}
IterationState::Object {
obj: obj.clone(),
current_key: None,
first_iteration: true,
}
}
Value::Set(set) => {
if set.is_empty() {
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
return Ok(());
}
IterationState::Set {
items: set.clone(),
current_item: None,
first_iteration: true,
}
}
_ => {
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
return Ok(());
}
};
let has_next =
self.setup_next_iteration(&iteration_state, params.key_reg, params.value_reg)?;
if !has_next {
self.pc = params.loop_end as usize;
return Ok(());
}
let loop_next_pc = params.loop_end - 1;
let body_resume_pc = compute_body_resume_pc(self.pc, params.body_start);
let loop_context = LoopContext {
mode: mode.clone(),
iteration_state,
key_reg: params.key_reg,
value_reg: params.value_reg,
result_reg: params.result_reg,
body_start: params.body_start,
loop_end: params.loop_end,
loop_next_pc,
body_resume_pc,
success_count: 0,
total_iterations: 0,
current_iteration_failed: false,
};
self.loop_stack.push(loop_context);
self.pc = params.body_start as usize - 1;
Ok(())
}
fn execute_loop_next_run_to_completion(
&mut self,
_body_start: u16,
loop_end: u16,
) -> Result<()> {
if let Some(mut loop_ctx) = self.loop_stack.pop() {
let body_start = loop_ctx.body_start;
let loop_end = loop_ctx.loop_end;
loop_ctx.total_iterations += 1;
let iteration_succeeded = self.check_iteration_success(&loop_ctx)?;
if iteration_succeeded {
loop_ctx.success_count += 1;
}
let action = self.determine_loop_action(&loop_ctx.mode, iteration_succeeded);
match action {
LoopAction::ExitWithSuccess => {
self.registers[loop_ctx.result_reg as usize] = Value::Bool(true);
self.pc = loop_end as usize - 1;
return Ok(());
}
LoopAction::ExitWithFailure => {
self.registers[loop_ctx.result_reg as usize] = Value::Bool(false);
self.pc = loop_end as usize - 1;
return Ok(());
}
LoopAction::Continue => {}
}
if let IterationState::Object {
ref mut current_key,
..
} = &mut loop_ctx.iteration_state
{
if loop_ctx.key_reg != loop_ctx.value_reg {
*current_key = Some(self.registers[loop_ctx.key_reg as usize].clone());
}
} else if let IterationState::Set {
ref mut current_item,
..
} = &mut loop_ctx.iteration_state
{
*current_item = Some(self.registers[loop_ctx.value_reg as usize].clone());
}
loop_ctx.iteration_state.advance();
let has_next = self.setup_next_iteration(
&loop_ctx.iteration_state,
loop_ctx.key_reg,
loop_ctx.value_reg,
)?;
if has_next {
loop_ctx.current_iteration_failed = false;
self.loop_stack.push(loop_ctx);
self.pc = body_start as usize - 1;
} else {
let final_result = match loop_ctx.mode {
LoopMode::Any => Value::Bool(loop_ctx.success_count > 0),
LoopMode::Every => {
Value::Bool(loop_ctx.success_count == loop_ctx.total_iterations)
}
LoopMode::ForEach => Value::Bool(loop_ctx.success_count > 0),
};
self.registers[loop_ctx.result_reg as usize] = final_result;
self.pc = loop_end as usize - 1;
}
Ok(())
} else {
self.pc = loop_end as usize;
Ok(())
}
}
fn execute_loop_start_suspendable(
&mut self,
mode: &LoopMode,
params: LoopParams,
) -> Result<()> {
let initial_result = match mode {
LoopMode::Any | LoopMode::Every | LoopMode::ForEach => Value::Bool(false),
};
self.registers[params.result_reg as usize] = initial_result.clone();
let collection_value = self.registers[params.collection as usize].clone();
let iteration_state = match &collection_value {
Value::Array(items) => {
if items.is_empty() {
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
return Ok(());
}
IterationState::Array {
items: items.clone(),
index: 0,
}
}
Value::Object(obj) => {
if obj.is_empty() {
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
return Ok(());
}
IterationState::Object {
obj: obj.clone(),
current_key: None,
first_iteration: true,
}
}
Value::Set(set) => {
if set.is_empty() {
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
return Ok(());
}
IterationState::Set {
items: set.clone(),
current_item: None,
first_iteration: true,
}
}
_ => {
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
return Ok(());
}
};
let has_next =
self.setup_next_iteration(&iteration_state, params.key_reg, params.value_reg)?;
if !has_next {
self.pc = params.loop_end as usize;
return Ok(());
}
let loop_next_pc = params.loop_end - 1;
let body_resume_pc = compute_body_resume_pc(self.pc, params.body_start);
let loop_context = LoopContext {
mode: mode.clone(),
iteration_state,
key_reg: params.key_reg,
value_reg: params.value_reg,
result_reg: params.result_reg,
body_start: params.body_start,
loop_end: params.loop_end,
loop_next_pc,
body_resume_pc,
success_count: 0,
total_iterations: 0,
current_iteration_failed: false,
};
let frame = ExecutionFrame::new(
params.body_start as usize,
FrameKind::Loop {
return_pc: params.loop_end as usize,
context: loop_context,
},
);
self.execution_stack.push(frame);
Ok(())
}
fn execute_loop_next_suspendable(&mut self, body_start: u16, loop_end: u16) -> Result<()> {
if !matches!(
self.execution_stack.last(),
Some(ExecutionFrame {
kind: FrameKind::Loop { .. },
..
})
) {
if let Some(frame) = self.execution_stack.last_mut() {
// Advance past the offending instruction so we do not repeatedly
// resume at the same LoopNext when the owning loop frame has
// already been popped (for example after a manual comprehension
// finalizes in suspendable mode).
let mut target_pc = loop_end as usize;
if target_pc <= self.pc {
target_pc = self.pc.saturating_add(1);
}
frame.pc = target_pc;
self.frame_pc_overridden = true;
}
return Ok(());
}
let (resume_pc, result_reg, loop_mode, iteration_succeeded) = {
let frame = self
.execution_stack
.last_mut()
.ok_or(VmError::AssertionFailed)?;
match &mut frame.kind {
FrameKind::Loop { return_pc, context } => {
context.total_iterations += 1;
let succeeded = !context.current_iteration_failed;
if succeeded {
context.success_count += 1;
}
(
*return_pc,
context.result_reg,
context.mode.clone(),
succeeded,
)
}
_ => return Err(VmError::AssertionFailed),
}
};
let action = self.determine_loop_action(&loop_mode, iteration_succeeded);
match action {
LoopAction::ExitWithSuccess => {
self.registers[result_reg as usize] = Value::Bool(true);
let completed_frame = self.execution_stack.pop().expect("loop frame exists");
if let Some(parent) = self.execution_stack.last_mut() {
parent.pc = resume_pc;
self.frame_pc_overridden = true;
}
drop(completed_frame);
Ok(())
}
LoopAction::ExitWithFailure => {
self.registers[result_reg as usize] = Value::Bool(false);
let completed_frame = self.execution_stack.pop().expect("loop frame exists");
if let Some(parent) = self.execution_stack.last_mut() {
parent.pc = resume_pc;
self.frame_pc_overridden = true;
}
drop(completed_frame);
Ok(())
}
LoopAction::Continue => {
let (mode, success_count, total_iterations, key_reg, value_reg, iteration_state) = {
let frame = self
.execution_stack
.last_mut()
.ok_or(VmError::AssertionFailed)?;
match &mut frame.kind {
FrameKind::Loop { context, .. } => {
if let IterationState::Object {
ref mut current_key,
..
} = &mut context.iteration_state
{
if context.key_reg != context.value_reg {
*current_key =
Some(self.registers[context.key_reg as usize].clone());
}
} else if let IterationState::Set {
ref mut current_item,
..
} = &mut context.iteration_state
{
*current_item =
Some(self.registers[context.value_reg as usize].clone());
}
context.iteration_state.advance();
context.current_iteration_failed = false;
(
context.mode.clone(),
context.success_count,
context.total_iterations,
context.key_reg,
context.value_reg,
context.iteration_state.clone(),
)
}
_ => return Err(VmError::AssertionFailed),
}
};
let has_next = self.setup_next_iteration(&iteration_state, key_reg, value_reg)?;
if has_next {
if let Some(frame) = self.execution_stack.last_mut() {
if let FrameKind::Loop { context, .. } = &frame.kind {
frame.pc = context.body_resume_pc;
} else {
frame.pc = body_start as usize;
}
self.frame_pc_overridden = true;
}
Ok(())
} else {
let final_result = match mode {
LoopMode::Any => Value::Bool(success_count > 0),
LoopMode::Every => Value::Bool(success_count == total_iterations),
LoopMode::ForEach => Value::Bool(success_count > 0),
};
self.registers[result_reg as usize] = final_result;
let completed_frame = self.execution_stack.pop().expect("loop frame exists");
if let Some(parent) = self.execution_stack.last_mut() {
parent.pc = resume_pc;
self.frame_pc_overridden = true;
}
drop(completed_frame);
Ok(())
}
}
}
}
fn handle_empty_collection(
&mut self,
mode: &LoopMode,
result_reg: u8,
loop_end: u16,
) -> Result<()> {
let result = match mode {
LoopMode::Any => Value::Bool(false),
LoopMode::Every => Value::Bool(true),
LoopMode::ForEach => Value::Bool(false),
};
self.registers[result_reg as usize] = result;
self.pc = (loop_end as usize).saturating_sub(1);
Ok(())
}
pub(super) fn setup_next_iteration(
&mut self,
state: &IterationState,
key_reg: u8,
value_reg: u8,
) -> Result<bool> {
match state {
IterationState::Array { items, index } => {
if *index < items.len() {
if key_reg != value_reg {
let key_value = Value::from(*index as f64);
self.registers[key_reg as usize] = key_value;
}
let item_value = items[*index].clone();
self.registers[value_reg as usize] = item_value;
Ok(true)
} else {
Ok(false)
}
}
IterationState::Object {
obj,
current_key,
first_iteration,
} => {
if *first_iteration {
if let Some((key, value)) = obj.iter().next() {
if key_reg != value_reg {
self.registers[key_reg as usize] = key.clone();
}
self.registers[value_reg as usize] = value.clone();
Ok(true)
} else {
Ok(false)
}
} else if let Some(ref current) = current_key {
let mut range_iter = obj.range((
core::ops::Bound::Excluded(current),
core::ops::Bound::Unbounded,
));
if let Some((key, value)) = range_iter.next() {
if key_reg != value_reg {
self.registers[key_reg as usize] = key.clone();
}
self.registers[value_reg as usize] = value.clone();
Ok(true)
} else {
Ok(false)
}
} else {
Ok(false)
}
}
IterationState::Set {
items,
current_item,
first_iteration,
} => {
if *first_iteration {
if let Some(item) = items.iter().next() {
if key_reg != value_reg {
self.registers[key_reg as usize] = item.clone();
}
self.registers[value_reg as usize] = item.clone();
Ok(true)
} else {
Ok(false)
}
} else if let Some(ref current) = current_item {
let mut range_iter = items.range((
core::ops::Bound::Excluded(current),
core::ops::Bound::Unbounded,
));
if let Some(item) = range_iter.next() {
if key_reg != value_reg {
self.registers[key_reg as usize] = item.clone();
}
self.registers[value_reg as usize] = item.clone();
Ok(true)
} else {
Ok(false)
}
} else {
Ok(false)
}
}
}
}
fn check_iteration_success(&self, loop_ctx: &LoopContext) -> Result<bool> {
Ok(!loop_ctx.current_iteration_failed)
}
fn determine_loop_action(&self, mode: &LoopMode, success: bool) -> LoopAction {
match (mode, success) {
(LoopMode::Any, true) => LoopAction::ExitWithSuccess,
(LoopMode::Every, false) => LoopAction::ExitWithFailure,
(LoopMode::ForEach, _) => LoopAction::Continue,
_ => LoopAction::Continue,
}
}
fn handle_condition_run_to_completion(&mut self, condition_passed: bool) -> Result<()> {
if condition_passed {
return Ok(());
}
if !self.loop_stack.is_empty() {
let (loop_mode, loop_next_pc, loop_end, result_reg) = {
let loop_ctx = self.loop_stack.last().unwrap();
(
loop_ctx.mode.clone(),
loop_ctx.loop_next_pc,
loop_ctx.loop_end,
loop_ctx.result_reg,
)
};
match loop_mode {
LoopMode::Any => {
if let Some(loop_ctx_mut) = self.loop_stack.last_mut() {
loop_ctx_mut.current_iteration_failed = true;
}
self.pc = loop_next_pc as usize - 1;
}
LoopMode::Every => {
self.loop_stack.pop();
self.pc = loop_end as usize - 1;
self.registers[result_reg as usize] = Value::Bool(false);
}
_ => {
if let Some(loop_ctx_mut) = self.loop_stack.last_mut() {
loop_ctx_mut.current_iteration_failed = true;
}
self.pc = loop_next_pc as usize - 1;
}
}
} else {
return Err(VmError::AssertionFailed);
}
Ok(())
}
fn handle_condition_suspendable(&mut self, condition_passed: bool) -> Result<()> {
if condition_passed {
return Ok(());
}
let (resume_pc, loop_ctx) = match self.execution_stack.last_mut() {
Some(ExecutionFrame {
kind: FrameKind::Loop { return_pc, context },
..
}) => (*return_pc, context),
_ => return Err(VmError::AssertionFailed),
};
match loop_ctx.mode {
LoopMode::Any | LoopMode::ForEach => {
loop_ctx.current_iteration_failed = true;
self.pc = loop_ctx.loop_next_pc as usize - 1;
}
LoopMode::Every => {
self.registers[loop_ctx.result_reg as usize] = Value::Bool(false);
let completed_frame = self.execution_stack.pop().expect("loop frame exists");
if let Some(parent) = self.execution_stack.last_mut() {
parent.pc = resume_pc;
}
drop(completed_frame);
}
}
Ok(())
}
}

325
src/rvm/vm/machine.rs Normal file
View File

@@ -0,0 +1,325 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::rvm::program::Program;
use crate::value::Value;
use crate::CompiledPolicy;
use alloc::collections::{btree_map::Entry, BTreeMap, VecDeque};
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
use super::context::{CallRuleContext, ComprehensionContext, LoopContext};
use super::errors::{Result, VmError};
use super::execution_model::{
BreakpointSet, ExecutionMode, ExecutionStack, ExecutionState, SuspendReason,
};
/// The Rego Virtual Machine
pub struct RegoVM {
/// Registers for storing values during execution
pub(super) registers: Vec<Value>,
/// Program counter
pub(super) pc: usize,
/// The compiled program containing instructions, literals, and metadata
pub(super) program: Arc<Program>,
/// Reference to the compiled policy for default rule access
pub(super) compiled_policy: Option<CompiledPolicy>,
/// Rule execution cache: rule_index -> (computed: bool, result: Value)
pub(super) rule_cache: Vec<(bool, Value)>,
/// Global data object
pub(super) data: Value,
/// Global input object
pub(super) input: Value,
/// Loop execution stack
/// Note: Loops are either at the outermost level (rule body) or within the topmost comprehension.
/// Loops never contain comprehensions - it's always the other way around.
pub(super) loop_stack: Vec<LoopContext>,
/// Call rule execution stack for managing nested rule calls
pub(super) call_rule_stack: Vec<CallRuleContext>,
/// Register stack for isolated register spaces during rule calls
pub(super) register_stack: Vec<Vec<Value>>,
/// Comprehension execution stack for tracking active comprehensions
/// Note: Comprehensions can be nested within each other, forming a proper nesting hierarchy.
/// Any loops within a comprehension belong to the topmost (current) comprehension context.
pub(super) comprehension_stack: Vec<ComprehensionContext>,
/// Base register window size for the main execution context
pub(super) base_register_count: usize,
/// Object pools for performance optimization
/// Pool of register windows for reuse during rule calls
pub(super) register_window_pool: Vec<Vec<Value>>,
/// Maximum number of instructions to execute (default: 25000)
pub(super) max_instructions: usize,
/// Current count of executed instructions
pub(super) executed_instructions: usize,
/// Cache for evaluated paths in virtual data document lookup
/// Structure: evaluated[path_component1][path_component2]...[Undefined] = result_value
pub(super) evaluated: Value,
/// Counter for cache hits during virtual data document lookup evaluation
pub(super) cache_hits: usize,
/// Explicit execution stack used when running in suspendable mode
pub(super) execution_stack: ExecutionStack,
/// Current execution state of the VM
pub(super) execution_state: ExecutionState,
/// Active breakpoints for the suspendable engine
pub(super) breakpoints: BreakpointSet,
/// Flag indicating whether single-step mode is active
pub(super) step_mode: bool,
/// Preloaded responses for HostAwait in run-to-completion execution keyed by identifier
pub(super) host_await_responses: BTreeMap<Value, VecDeque<Value>>,
/// Current execution mode (run-to-completion vs suspendable)
pub(super) execution_mode: ExecutionMode,
/// Tracks whether the current top-of-stack frame PC was explicitly set by an instruction
pub(super) frame_pc_overridden: bool,
/// Whether builtins should raise errors strictly or return undefined on failure
pub(super) strict_builtin_errors: bool,
}
impl Default for RegoVM {
fn default() -> Self {
Self::new()
}
}
impl RegoVM {
/// Create a new virtual machine
pub fn new() -> Self {
RegoVM {
registers: Vec::new(), // Start with no registers - will be resized when program is loaded
pc: 0,
program: Arc::new(Program::default()),
compiled_policy: None,
rule_cache: Vec::new(),
data: Value::Null,
input: Value::Null,
loop_stack: Vec::new(),
call_rule_stack: Vec::new(),
register_stack: Vec::new(),
comprehension_stack: Vec::new(),
base_register_count: 2, // Default to 2 registers for basic operations
register_window_pool: Vec::new(), // Initialize register window pool
max_instructions: 25000, // Default maximum instruction limit
executed_instructions: 0,
evaluated: Value::new_object(), // Initialize evaluation cache
cache_hits: 0, // Initialize cache hit counter
execution_stack: ExecutionStack::new(),
execution_state: ExecutionState::Ready,
breakpoints: BreakpointSet::new(),
step_mode: false,
host_await_responses: BTreeMap::new(),
execution_mode: ExecutionMode::RunToCompletion,
frame_pc_overridden: false,
strict_builtin_errors: false,
}
}
/// Create a new virtual machine with compiled policy for default rule support
pub fn new_with_policy(compiled_policy: CompiledPolicy) -> Self {
let mut vm = Self::new();
vm.compiled_policy = Some(compiled_policy);
vm
}
/// Load a complete program for execution
pub fn load_program(&mut self, program: Arc<Program>) {
self.program = program.clone();
// Use the dispatch window size from the program for initial register allocation
let dispatch_size = program.dispatch_window_size.max(2); // Ensure at least 2 registers
self.base_register_count = dispatch_size;
// Resize registers to match program requirements
self.registers.clear();
self.registers.resize(dispatch_size, Value::Undefined);
// Initialize rule cache
self.rule_cache = vec![(false, Value::Undefined); program.rule_infos.len()];
// Set PC to main entry point
self.pc = program.main_entry_point;
self.executed_instructions = 0; // Reset instruction counter
}
/// Set the compiled policy for default rule evaluation
pub fn set_compiled_policy(&mut self, compiled_policy: CompiledPolicy) {
self.compiled_policy = Some(compiled_policy);
}
/// Set the maximum number of instructions that can be executed
pub fn set_max_instructions(&mut self, max: usize) {
self.max_instructions = max;
}
/// Set the base register count for the main execution context
/// This determines how many registers are available in the root register window
pub fn set_base_register_count(&mut self, count: usize) {
self.base_register_count = count.max(1); // Ensure at least 1 register
if !self.registers.is_empty() {
self.registers
.resize(self.base_register_count, Value::Undefined);
}
}
/// Set the global data object
pub fn set_data(&mut self, data: Value) -> Result<()> {
// Check for conflicts between rule tree and data
self.program.check_rule_data_conflicts(&data)?;
self.data = data;
Ok(())
}
/// Set the global input object
pub fn set_input(&mut self, input: Value) {
self.input = input;
}
/// Get the number of entry points available
pub fn get_entry_point_count(&self) -> usize {
self.program.entry_points.len()
}
/// Get all entry point names
pub fn get_entry_point_names(&self) -> Vec<String> {
self.program.entry_points.keys().cloned().collect()
}
// Public getters for visualization
pub fn get_pc(&self) -> usize {
self.pc
}
pub fn get_registers(&self) -> &Vec<Value> {
&self.registers
}
pub fn get_program(&self) -> &Arc<Program> {
&self.program
}
pub fn get_call_stack(&self) -> &Vec<CallRuleContext> {
&self.call_rule_stack
}
pub fn get_loop_stack(&self) -> &Vec<LoopContext> {
&self.loop_stack
}
pub fn get_cache_hits(&self) -> usize {
self.cache_hits
}
/// Set the execution mode for the VM
pub fn set_execution_mode(&mut self, mode: ExecutionMode) {
self.execution_mode = mode;
}
/// Configure whether builtin operations should raise errors strictly
pub fn set_strict_builtin_errors(&mut self, strict: bool) {
self.strict_builtin_errors = strict;
}
/// Returns whether builtin operations raise errors strictly
pub fn strict_builtin_errors(&self) -> bool {
self.strict_builtin_errors
}
/// Enable or disable single-step execution for suspendable runs
pub fn set_step_mode(&mut self, enabled: bool) {
self.step_mode = enabled;
}
/// Configure the sequence of HostAwait responses for run-to-completion execution
pub fn set_host_await_responses<I, J>(&mut self, responses: I)
where
I: IntoIterator<Item = (Value, J)>,
J: IntoIterator<Item = Value>,
{
self.host_await_responses.clear();
for (identifier, values) in responses {
let mut queue = VecDeque::new();
queue.extend(values);
match self.host_await_responses.entry(identifier) {
Entry::Vacant(entry) => {
entry.insert(queue);
}
Entry::Occupied(mut entry) => {
entry.get_mut().extend(queue);
}
}
}
}
pub(super) fn next_host_await_response(
&mut self,
identifier: &Value,
dest: u8,
) -> Result<Value> {
let missing_error = || VmError::HostAwaitResponseMissing {
dest,
identifier: identifier.clone(),
};
let (response, should_remove) = {
let queue = self
.host_await_responses
.get_mut(identifier)
.ok_or_else(missing_error)?;
let response = queue.pop_front().ok_or_else(missing_error)?;
let should_remove = queue.is_empty();
(response, should_remove)
};
if should_remove {
self.host_await_responses.remove(identifier);
}
Ok(response)
}
/// Get the current execution mode
pub fn get_execution_mode(&self) -> ExecutionMode {
self.execution_mode
}
/// Get the current execution state of the VM
pub fn execution_state(&self) -> &ExecutionState {
&self.execution_state
}
/// Get the suspend reason if the VM is currently suspended
pub fn suspend_reason(&self) -> Option<&SuspendReason> {
match &self.execution_state {
ExecutionState::Suspended { reason, .. } => Some(reason),
_ => None,
}
}
}

23
src/rvm/vm/mod.rs Normal file
View File

@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
extern crate alloc;
mod arithmetic;
mod comprehension;
mod context;
mod dispatch;
mod errors;
mod execution;
mod execution_model;
mod functions;
mod loops;
mod machine;
mod rules;
mod state;
mod virtual_data;
pub use context::{CallRuleContext, IterationState, LoopContext};
pub use errors::{Result, VmError};
pub use execution_model::{ExecutionMode, ExecutionState, SuspendReason};
pub use machine::RegoVM;

627
src/rvm/vm/rules.rs Normal file
View File

@@ -0,0 +1,627 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::rvm::instructions::FunctionCallParams;
use crate::rvm::program::{RuleInfo, RuleType};
use crate::value::Value;
use alloc::vec::Vec;
use core::mem;
use super::context::CallRuleContext;
use super::errors::{Result, VmError};
use super::execution_model::{
ExecutionFrame, ExecutionMode, FrameKind, RuleFrameData, RuleFramePhase,
};
use super::machine::RegoVM;
impl RegoVM {
pub(super) fn execute_rule_definitions_common(
&mut self,
rule_definitions: &[Vec<u32>],
rule_info: &RuleInfo,
function_call_params: Option<&FunctionCallParams>,
) -> Result<(Value, bool)> {
let mut first_successful_result: Option<Value> = None;
let mut rule_failed_due_to_inconsistency = false;
let is_function_call = rule_info.function_info.is_some();
let result_reg = rule_info.result_reg as usize;
let num_registers = rule_info.num_registers as usize;
let mut register_window = self.new_register_window();
register_window.clear();
register_window.reserve(num_registers);
register_window.push(Value::Undefined);
let num_retained_registers = match function_call_params {
Some(params) => {
for arg in params.args[0..params.num_args as usize].iter() {
register_window.push(self.registers[*arg as usize].clone());
}
params.num_args as usize + 1
}
_ => match rule_info.rule_type {
RuleType::PartialSet | RuleType::PartialObject => 1,
RuleType::Complete => 0,
},
};
let mut old_registers = Vec::default();
mem::swap(&mut old_registers, &mut self.registers);
let mut old_loop_stack = Vec::default();
mem::swap(&mut old_loop_stack, &mut self.loop_stack);
let mut old_comprehension_stack = Vec::default();
mem::swap(&mut old_comprehension_stack, &mut self.comprehension_stack);
self.register_stack.push(old_registers);
self.registers = register_window;
'outer: for (def_idx, definition_bodies) in rule_definitions.iter().enumerate() {
for (body_entry_point_idx, body_entry_point) in definition_bodies.iter().enumerate() {
if let Some(ctx) = self.call_rule_stack.last_mut() {
ctx.current_body_index = body_entry_point_idx;
ctx.current_definition_index = def_idx;
}
self.registers
.resize(num_retained_registers, Value::Undefined);
self.registers.resize(num_registers, Value::Undefined);
if let Some(destructuring_entry_point) =
rule_info.destructuring_blocks.get(def_idx).and_then(|x| *x)
{
match self.jump_to(destructuring_entry_point as usize) {
Ok(_result) => {}
Err(_e) => {
continue 'outer;
}
}
}
match self.jump_to(*body_entry_point as usize) {
Ok(_) => {
if matches!(rule_info.rule_type, RuleType::Complete) || is_function_call {
let current_result = self.registers[result_reg].clone();
if current_result != Value::Undefined {
if let Some(ref expected) = first_successful_result {
if *expected != current_result {
rule_failed_due_to_inconsistency = true;
self.registers[result_reg] = Value::Undefined;
break;
}
} else {
first_successful_result = Some(current_result.clone());
}
}
}
}
Err(_e) => {
continue;
}
}
}
if rule_failed_due_to_inconsistency {
break;
}
}
let final_result = if rule_failed_due_to_inconsistency {
Value::Undefined
} else if let Some(successful_result) = first_successful_result {
successful_result
} else {
self.registers[result_reg].clone()
};
if let Some(old_registers) = self.register_stack.pop() {
let mut current_register_window = Vec::default();
mem::swap(&mut current_register_window, &mut self.registers);
self.return_register_window(current_register_window);
self.registers = old_registers;
}
self.loop_stack = old_loop_stack;
self.comprehension_stack = old_comprehension_stack;
Ok((final_result, rule_failed_due_to_inconsistency))
}
pub(super) fn execute_call_rule_common(
&mut self,
dest: u8,
rule_index: u16,
function_call_params: Option<&FunctionCallParams>,
) -> Result<()> {
let rule_idx = rule_index as usize;
if rule_idx >= self.rule_cache.len() {
return Err(VmError::RuleIndexOutOfBounds { index: rule_index });
}
let rule_info = self
.program
.rule_infos
.get(rule_idx)
.ok_or(VmError::RuleInfoMissing { index: rule_index })?
.clone();
let is_function_rule = rule_info.function_info.is_some();
if !is_function_rule {
let (computed, cached_result) = &self.rule_cache[rule_idx];
if *computed {
self.registers[dest as usize] = cached_result.clone();
return Ok(());
}
}
let rule_type = rule_info.rule_type.clone();
let rule_definitions = rule_info.definitions.clone();
if rule_definitions.is_empty() {
let result = Value::Undefined;
if !is_function_rule {
self.rule_cache[rule_idx] = (true, result.clone());
}
self.registers[dest as usize] = result;
return Ok(());
}
self.call_rule_stack.push(CallRuleContext {
return_pc: self.pc,
dest_reg: dest,
result_reg: rule_info.result_reg,
rule_index,
rule_type: rule_type.clone(),
current_definition_index: 0,
current_body_index: 0,
});
let (final_result, rule_failed_due_to_inconsistency) = self
.execute_rule_definitions_common(&rule_definitions, &rule_info, function_call_params)?;
self.registers[dest as usize] = Value::Undefined;
let call_context = self.call_rule_stack.pop().expect("Call stack underflow");
self.pc = call_context.return_pc;
let result_from_rule = if !rule_failed_due_to_inconsistency {
final_result
} else {
Value::Undefined
};
self.registers[dest as usize] = result_from_rule.clone();
if self.registers[dest as usize] == Value::Undefined && !rule_failed_due_to_inconsistency {
match call_context.rule_type {
RuleType::PartialSet => {
self.registers[dest as usize] = Value::new_set();
}
RuleType::PartialObject => {
self.registers[dest as usize] = Value::new_object();
}
RuleType::Complete => {
if let Some(rule_info) = self
.program
.rule_infos
.get(call_context.rule_index as usize)
{
if let Some(default_literal_index) = rule_info.default_literal_index {
if let Some(default_value) =
self.program.literals.get(default_literal_index as usize)
{
self.registers[dest as usize] = default_value.clone();
}
}
}
}
}
}
let final_result = self.registers[dest as usize].clone();
if !is_function_rule {
self.rule_cache[rule_idx] = (true, final_result);
}
Ok(())
}
pub(super) fn execute_call_rule(&mut self, dest: u8, rule_index: u16) -> Result<()> {
match self.execution_mode {
ExecutionMode::RunToCompletion => self.execute_call_rule_common(dest, rule_index, None),
ExecutionMode::Suspendable => {
self.execute_call_rule_suspendable(dest, rule_index, None)
}
}
}
pub(super) fn execute_call_rule_suspendable(
&mut self,
dest: u8,
rule_index: u16,
function_call_params: Option<&FunctionCallParams>,
) -> Result<()> {
let rule_idx = rule_index as usize;
if rule_idx >= self.rule_cache.len() {
return Err(VmError::RuleIndexOutOfBounds { index: rule_index });
}
let rule_info = self
.program
.rule_infos
.get(rule_idx)
.ok_or(VmError::RuleInfoMissing { index: rule_index })?
.clone();
let is_function_rule = rule_info.function_info.is_some();
if !is_function_rule {
let (computed, cached_result) = &self.rule_cache[rule_idx];
if *computed {
self.registers[dest as usize] = cached_result.clone();
return Ok(());
}
}
if rule_info.definitions.is_empty() {
let result = Value::Undefined;
if !is_function_rule {
self.rule_cache[rule_idx] = (true, result.clone());
}
if self.registers.len() <= dest as usize {
self.registers.resize(dest as usize + 1, Value::Undefined);
}
self.registers[dest as usize] = result;
return Ok(());
}
let num_registers = rule_info.num_registers as usize;
let num_retained_registers = match function_call_params {
Some(params) => params.arg_count() + 1,
None => match rule_info.rule_type {
RuleType::PartialSet | RuleType::PartialObject => 1,
RuleType::Complete => 0,
},
};
let mut register_window = self.new_register_window();
register_window.clear();
register_window.reserve(num_registers);
register_window.push(Value::Undefined);
if let Some(params) = function_call_params {
for &arg in params.arg_registers() {
register_window.push(self.registers[arg as usize].clone());
}
}
let mut saved_registers = Vec::default();
mem::swap(&mut saved_registers, &mut self.registers);
self.registers = register_window;
let mut saved_loop_stack = Vec::default();
mem::swap(&mut saved_loop_stack, &mut self.loop_stack);
let mut saved_comprehension_stack = Vec::default();
mem::swap(
&mut saved_comprehension_stack,
&mut self.comprehension_stack,
);
self.loop_stack.clear();
self.comprehension_stack.clear();
self.call_rule_stack.push(CallRuleContext {
return_pc: self.pc,
dest_reg: dest,
result_reg: rule_info.result_reg,
rule_index,
rule_type: rule_info.rule_type.clone(),
current_definition_index: 0,
current_body_index: 0,
});
let mut frame_data = RuleFrameData {
return_pc: self.pc,
dest_reg: dest,
rule_index,
current_definition_index: 0,
current_body_index: 0,
total_definitions: rule_info.definitions.len(),
phase: RuleFramePhase::Initializing,
accumulated_result: None,
any_body_succeeded: false,
rule_failed_due_to_inconsistency: false,
rule_type: rule_info.rule_type.clone(),
result_reg: rule_info.result_reg,
is_function_rule,
num_registers,
num_retained_registers,
saved_registers,
saved_loop_stack,
saved_comprehension_stack,
};
let initial_pc = self
.prepare_rule_frame_initial_pc(&mut frame_data, &rule_info)?
.ok_or_else(|| VmError::Internal("Rule frame has no initial PC".into()))?;
let frame = ExecutionFrame::new(initial_pc, FrameKind::Rule(frame_data));
self.execution_stack.push(frame);
Ok(())
}
pub(super) fn execute_rule_init(&mut self, result_reg: u8, _rule_index: u16) -> Result<()> {
let current_ctx = self
.call_rule_stack
.last_mut()
.expect("Call stack underflow");
current_ctx.result_reg = result_reg;
match current_ctx.rule_type {
RuleType::Complete => {
self.registers[result_reg as usize] = Value::Undefined;
}
RuleType::PartialSet => {
if current_ctx.current_definition_index == 0 && current_ctx.current_body_index == 0
{
self.registers[result_reg as usize] = Value::new_set();
}
}
RuleType::PartialObject => {
if current_ctx.current_definition_index == 0 && current_ctx.current_body_index == 0
{
self.registers[result_reg as usize] = Value::new_object();
}
}
}
Ok(())
}
pub(super) fn execute_rule_return(&mut self) -> Result<()> {
Ok(())
}
fn prepare_rule_frame_initial_pc(
&mut self,
frame_data: &mut RuleFrameData,
rule_info: &RuleInfo,
) -> Result<Option<usize>> {
frame_data.current_definition_index = 0;
frame_data.current_body_index = 0;
frame_data.phase = RuleFramePhase::Initializing;
self.rule_frame_schedule_segment(frame_data, rule_info)
}
fn rule_frame_schedule_segment(
&mut self,
frame_data: &mut RuleFrameData,
rule_info: &RuleInfo,
) -> Result<Option<usize>> {
if frame_data.rule_failed_due_to_inconsistency {
frame_data.phase = RuleFramePhase::Finalizing;
return Ok(None);
}
while frame_data.current_definition_index < frame_data.total_definitions {
let definition_bodies = &rule_info.definitions[frame_data.current_definition_index];
if frame_data.current_body_index < definition_bodies.len() {
if let Some(ctx) = self.call_rule_stack.last_mut() {
ctx.current_definition_index = frame_data.current_definition_index;
ctx.current_body_index = frame_data.current_body_index;
}
self.registers
.resize(frame_data.num_retained_registers, Value::Undefined);
self.registers
.resize(frame_data.num_registers, Value::Undefined);
if let Some(destructuring_entry_point) = rule_info
.destructuring_blocks
.get(frame_data.current_definition_index)
.and_then(|opt| *opt)
{
frame_data.phase = RuleFramePhase::ExecutingDestructuring;
return Ok(Some(destructuring_entry_point as usize));
} else {
frame_data.phase = RuleFramePhase::ExecutingBody;
return Ok(Some(
definition_bodies[frame_data.current_body_index] as usize,
));
}
} else {
frame_data.current_definition_index += 1;
frame_data.current_body_index = 0;
}
}
frame_data.phase = RuleFramePhase::Finalizing;
Ok(None)
}
fn rule_frame_after_destructuring_success(
&mut self,
frame_data: &mut RuleFrameData,
rule_info: &RuleInfo,
) -> Result<Option<usize>> {
frame_data.phase = RuleFramePhase::ExecutingBody;
let definition_bodies = &rule_info.definitions[frame_data.current_definition_index];
if frame_data.current_body_index >= definition_bodies.len() {
frame_data.current_body_index += 1;
return self.rule_frame_schedule_segment(frame_data, rule_info);
}
Ok(Some(
definition_bodies[frame_data.current_body_index] as usize,
))
}
fn rule_frame_after_failure(
&mut self,
frame_data: &mut RuleFrameData,
rule_info: &RuleInfo,
) -> Result<Option<usize>> {
frame_data.current_body_index += 1;
self.rule_frame_schedule_segment(frame_data, rule_info)
}
fn rule_frame_after_success(
&mut self,
frame_data: &mut RuleFrameData,
rule_info: &RuleInfo,
) -> Result<Option<usize>> {
frame_data.any_body_succeeded = true;
if matches!(frame_data.rule_type, RuleType::Complete) || frame_data.is_function_rule {
let current_result = self
.registers
.get(frame_data.result_reg as usize)
.cloned()
.unwrap_or(Value::Undefined);
if current_result != Value::Undefined {
if let Some(expected) = &frame_data.accumulated_result {
if *expected != current_result {
frame_data.rule_failed_due_to_inconsistency = true;
if let Some(result_slot) =
self.registers.get_mut(frame_data.result_reg as usize)
{
*result_slot = Value::Undefined;
}
}
} else {
frame_data.accumulated_result = Some(current_result);
}
}
}
frame_data.current_body_index += 1;
self.rule_frame_schedule_segment(frame_data, rule_info)
}
pub(super) fn finalize_rule_frame_data(&mut self, frame_data: RuleFrameData) -> Result<Value> {
let RuleFrameData {
return_pc,
dest_reg,
rule_index,
accumulated_result,
rule_failed_due_to_inconsistency,
rule_type,
result_reg,
is_function_rule,
saved_registers,
saved_loop_stack,
saved_comprehension_stack,
..
} = frame_data;
let rule_idx = rule_index as usize;
let rule_info = self
.program
.rule_infos
.get(rule_idx)
.ok_or(VmError::RuleInfoMissing { index: rule_index })?
.clone();
let result_from_rule = if rule_failed_due_to_inconsistency {
Value::Undefined
} else if let Some(value) = accumulated_result {
value
} else {
self.registers
.get(result_reg as usize)
.cloned()
.unwrap_or(Value::Undefined)
};
let mut current_window = Vec::default();
mem::swap(&mut current_window, &mut self.registers);
self.return_register_window(current_window);
self.loop_stack = saved_loop_stack;
self.comprehension_stack = saved_comprehension_stack;
let mut parent_registers = saved_registers;
if parent_registers.len() <= dest_reg as usize {
parent_registers.resize(dest_reg as usize + 1, Value::Undefined);
}
parent_registers[dest_reg as usize] = result_from_rule.clone();
if parent_registers[dest_reg as usize] == Value::Undefined
&& !rule_failed_due_to_inconsistency
{
match rule_type {
RuleType::PartialSet => parent_registers[dest_reg as usize] = Value::new_set(),
RuleType::PartialObject => {
parent_registers[dest_reg as usize] = Value::new_object()
}
RuleType::Complete => {
if let Some(default_literal_index) = rule_info.default_literal_index {
if let Some(default_value) =
self.program.literals.get(default_literal_index as usize)
{
parent_registers[dest_reg as usize] = default_value.clone();
}
}
}
}
}
let final_value = parent_registers[dest_reg as usize].clone();
if !is_function_rule {
self.rule_cache[rule_idx] = (true, final_value.clone());
}
self.registers = parent_registers;
if self.call_rule_stack.pop().is_none() {
return Err(VmError::Internal(alloc::format!(
"Call rule stack underflow during rule finalization | {}",
self.get_debug_state()
)));
}
self.pc = return_pc;
Ok(final_value)
}
pub(super) fn handle_rule_break_event(
&mut self,
frame_data: &mut RuleFrameData,
) -> Result<Option<usize>> {
let rule_info = self.get_rule_info(frame_data.rule_index)?;
match frame_data.phase {
RuleFramePhase::ExecutingDestructuring => {
self.rule_frame_after_destructuring_success(frame_data, &rule_info)
}
RuleFramePhase::ExecutingBody => self.rule_frame_after_success(frame_data, &rule_info),
RuleFramePhase::Initializing | RuleFramePhase::Finalizing => Ok(None),
}
}
pub(super) fn handle_rule_error_event(
&mut self,
frame_data: &mut RuleFrameData,
) -> Result<Option<usize>> {
let rule_info = self.get_rule_info(frame_data.rule_index)?;
self.rule_frame_after_failure(frame_data, &rule_info)
}
fn get_rule_info(&self, rule_index: u16) -> Result<RuleInfo> {
let idx = rule_index as usize;
self.program
.rule_infos
.get(idx)
.cloned()
.ok_or(VmError::RuleInfoMissing { index: rule_index })
}
}

107
src/rvm/vm/state.rs Normal file
View File

@@ -0,0 +1,107 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::value::Value;
use alloc::string::String;
use alloc::vec::Vec;
use super::errors::{Result, VmError};
use super::execution_model::ExecutionState;
use super::machine::RegoVM;
impl RegoVM {
/// Reset all execution state and return objects to pools for reuse
pub(super) fn reset_execution_state(&mut self) {
// Reset basic execution state
self.executed_instructions = 0;
self.pc = 0;
self.evaluated = Value::new_object();
self.cache_hits = 0;
// Reset suspendable execution state
self.execution_stack.clear();
self.execution_state = ExecutionState::Ready;
// Return objects to pools and clear stacks
self.return_to_pools();
// Reset rule cache
self.rule_cache = alloc::vec![(false, Value::Undefined); self.program.rule_infos.len()];
// Reset registers to clean state
self.registers.clear();
self.registers
.resize(self.base_register_count, Value::Undefined);
}
/// Return all active objects to their respective pools for reuse
pub(super) fn return_to_pools(&mut self) {
// Clear stacks - these are small structs that don't need pooling
self.loop_stack.clear();
self.call_rule_stack.clear();
self.comprehension_stack.clear();
// Return register windows to pool for reuse
while let Some(registers) = self.register_stack.pop() {
self.return_register_window(registers);
}
}
/// Get a register window from the pool or create a new one
pub(super) fn new_register_window(&mut self) -> Vec<Value> {
self.register_window_pool.pop().unwrap_or_default()
}
/// Return a register window to the pool for reuse
pub(super) fn return_register_window(&mut self, mut window: Vec<Value>) {
window.clear(); // Clear contents for reuse
self.register_window_pool.push(window);
}
/// Validate VM state consistency for debugging
pub(super) fn validate_vm_state(&self) -> Result<()> {
// Check register bounds
if self.registers.len() < self.base_register_count {
return Err(VmError::Internal(alloc::format!(
"Register count {} < base count {}",
self.registers.len(),
self.base_register_count
)));
}
// Check PC bounds
if self.pc >= self.program.instructions.len() {
return Err(VmError::Internal(alloc::format!(
"PC {} >= instruction count {}",
self.pc,
self.program.instructions.len()
)));
}
// Check rule cache bounds
if self.rule_cache.len() != self.program.rule_infos.len() {
return Err(VmError::Internal(alloc::format!(
"Rule cache size {} != rule info count {}",
self.rule_cache.len(),
self.program.rule_infos.len()
)));
}
Ok(())
}
/// Get current VM state for debugging
pub(super) fn get_debug_state(&self) -> String {
alloc::format!(
"VM State: PC={}, registers={}, executed={}/{}, stacks: loop={}, call={}, register={}, comprehension={}",
self.pc,
self.registers.len(),
self.executed_instructions,
self.max_instructions,
self.loop_stack.len(),
self.call_rule_stack.len(),
self.register_stack.len(),
self.comprehension_stack.len()
)
}
}

293
src/rvm/vm/virtual_data.rs Normal file
View File

@@ -0,0 +1,293 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::rvm::instructions::LiteralOrRegister;
use crate::value::Value;
use alloc::vec::Vec;
use super::errors::{Result, VmError};
use super::machine::RegoVM;
impl RegoVM {
pub(super) fn execute_virtual_data_document_lookup_subobject(
&mut self,
path_components: &[LiteralOrRegister],
rule_tree_subobject: &Value,
) -> Result<Value> {
let mut root_path = Vec::new();
for component in path_components {
let key_value = match component {
LiteralOrRegister::Literal(idx) => self
.program
.literals
.get(*idx as usize)
.ok_or(VmError::LiteralIndexOutOfBounds {
index: *idx as usize,
})?
.clone(),
LiteralOrRegister::Register(reg) => self.registers[*reg as usize].clone(),
};
root_path.push(key_value);
}
let mut data_subobject = self.data.clone();
for path_component in &root_path {
data_subobject = data_subobject[path_component].clone();
}
let mut result_subobject = match data_subobject {
Value::Undefined => Value::new_object(),
_ => data_subobject,
};
self.traverse_rule_tree_subobject(rule_tree_subobject, &mut result_subobject, &root_path)?;
Ok(result_subobject)
}
fn set_nested_value(&self, target: &mut Value, path: &[Value], value: Value) -> Result<()> {
Self::set_nested_value_static(target, path, value)
}
fn set_nested_value_static(target: &mut Value, path: &[Value], value: Value) -> Result<()> {
if path.is_empty() {
*target = value;
return Ok(());
}
if *target == Value::Undefined {
*target = Value::new_object();
}
if let Value::Object(ref mut map) = target {
let key = &path[0];
if !map.contains_key(key) {
crate::Rc::make_mut(map).insert(key.clone(), Value::Undefined);
}
if let Some(next_target) = crate::Rc::make_mut(map).get_mut(key) {
Self::set_nested_value_static(next_target, &path[1..], value)?;
}
} else {
return Err(VmError::InvalidRuleTreeEntry {
value: target.clone(),
});
}
Ok(())
}
fn traverse_rule_tree_subobject(
&mut self,
rule_tree_node: &Value,
result_subobject: &mut Value,
root_path: &[Value],
) -> Result<()> {
self.traverse_rule_tree_subobject_with_path(
rule_tree_node,
result_subobject,
root_path,
&[],
)
}
fn traverse_rule_tree_subobject_with_path(
&mut self,
rule_tree_node: &Value,
result_subobject: &mut Value,
root_path: &[Value],
relative_path: &[Value],
) -> Result<()> {
match rule_tree_node {
Value::Number(rule_idx) => {
if let Some(rule_index) = rule_idx.as_u64() {
let mut full_cache_path = root_path.to_vec();
full_cache_path.extend_from_slice(relative_path);
let cached_result = {
let mut cache_lookup = &self.evaluated;
let mut path_exists = true;
for path_component in &full_cache_path {
if let Value::Object(ref map) = cache_lookup {
if let Some(next_value) = map.get(path_component) {
cache_lookup = next_value;
} else {
path_exists = false;
break;
}
} else {
path_exists = false;
break;
}
}
if path_exists {
if let Value::Object(ref map) = cache_lookup {
map.get(&Value::Undefined).cloned()
} else {
None
}
} else {
None
}
};
let rule_result = if let Some(cached) = cached_result {
self.cache_hits += 1;
cached
} else {
let temp_reg = self.registers.len() as u8;
self.registers.push(Value::Undefined);
self.execute_call_rule_common(temp_reg, rule_index as u16, None)?;
let result = self.registers.pop().unwrap();
let mut cache_path = full_cache_path.clone();
cache_path.push(Value::Undefined);
Self::set_nested_value_static(
&mut self.evaluated,
&cache_path,
result.clone(),
)?;
result
};
self.set_nested_value(result_subobject, relative_path, rule_result)?;
} else {
return Err(VmError::InvalidRuleIndex {
rule_index: Value::Number(rule_idx.clone()),
});
}
}
Value::Object(obj) => {
for (key, value) in obj.iter() {
let mut new_relative_path = relative_path.to_vec();
new_relative_path.push(key.clone());
self.traverse_rule_tree_subobject_with_path(
value,
result_subobject,
root_path,
&new_relative_path,
)?;
}
}
_ => {}
}
Ok(())
}
pub(super) fn execute_virtual_data_document_lookup(&mut self, params_index: u16) -> Result<()> {
let params = self
.program
.instruction_data
.get_virtual_data_document_lookup_params(params_index)
.ok_or(VmError::InvalidVirtualDataDocumentLookupParams {
index: params_index,
})?
.clone();
let mut current_node = &self.program.rule_tree["data"];
let mut components_consumed = 0;
for (i, component) in params.path_components.iter().enumerate() {
let key_value = match component {
LiteralOrRegister::Literal(idx) => self
.program
.literals
.get(*idx as usize)
.ok_or(VmError::LiteralIndexOutOfBounds {
index: *idx as usize,
})?
.clone(),
LiteralOrRegister::Register(reg) => self.registers[*reg as usize].clone(),
};
current_node = &current_node[&key_value];
components_consumed = i + 1;
match current_node {
Value::Undefined | Value::Number(_) => break,
_ => {}
}
}
match current_node {
Value::Number(rule_index_value) => {
if let Some(rule_index) = rule_index_value.as_u64() {
let rule_index = rule_index as u16;
self.execute_call_rule_common(params.dest, rule_index, None)?;
if components_consumed < params.path_components.len() {
let mut rule_result = self.registers[params.dest as usize].clone();
for component in &params.path_components[components_consumed..] {
let key_value = match component {
LiteralOrRegister::Literal(idx) => self
.program
.literals
.get(*idx as usize)
.ok_or(VmError::LiteralIndexOutOfBounds {
index: *idx as usize,
})?
.clone(),
LiteralOrRegister::Register(reg) => {
self.registers[*reg as usize].clone()
}
};
rule_result = rule_result[&key_value].clone();
}
self.registers[params.dest as usize] = rule_result;
}
} else {
return Err(VmError::InvalidRuleIndex {
rule_index: Value::Number(rule_index_value.clone()),
});
}
}
Value::Undefined | Value::Object(_)
if components_consumed != params.path_components.len() =>
{
let mut result = self.data.clone();
for component in &params.path_components {
let key_value = match component {
LiteralOrRegister::Literal(idx) => self
.program
.literals
.get(*idx as usize)
.ok_or(VmError::LiteralIndexOutOfBounds {
index: *idx as usize,
})?
.clone(),
LiteralOrRegister::Register(reg) => self.registers[*reg as usize].clone(),
};
result = result[&key_value].clone();
}
self.registers[params.dest as usize] = result;
}
Value::Object(_) => {
let rule_tree_subobject = current_node.clone();
let result = self.execute_virtual_data_document_lookup_subobject(
&params.path_components,
&rule_tree_subobject,
)?;
self.registers[params.dest as usize] = result;
}
_ => {
return Err(VmError::InvalidRuleTreeEntry {
value: current_node.clone(),
});
}
}
Ok(())
}
}

View File

@@ -1,5 +1,5 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
mod interpreter;
pub mod interpreter;
mod scheduler;

View File

@@ -4,6 +4,7 @@
use crate::number::Number;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::vec::Vec;
use core::fmt;
use core::ops;
@@ -67,7 +68,7 @@ impl Serialize for Value {
{
use serde::ser::Error;
match self {
Value::Null => serializer.serialize_none(),
Value::Null => serializer.serialize_unit(),
Value::Bool(b) => serializer.serialize_bool(*b),
Value::String(s) => serializer.serialize_str(s.as_ref()),
Value::Number(n) => n.serialize(serializer),
@@ -118,6 +119,20 @@ impl<'de> Visitor<'de> for ValueVisitor {
Ok(Value::Bool(v))
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(Value::Null)
}
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
Value::deserialize(deserializer)
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: de::Error,

78
tests/rvm/vm/README.md Normal file
View File

@@ -0,0 +1,78 @@
# Regorus VM Test Suites
This directory hosts YAML-driven regression suites for the Regorus virtual machine. Each YAML file is converted into parameterised Rust tests by `src/rvm/tests/vm.rs`, so the contents here define the end-to-end VM coverage.
## Prerequisites
- Enable the `rvm` feature (it pulls in `std` and the VM runtime) whenever you run these tests:
- `cargo test --features rvm run_vm_test_file`
- `cargo test --features rvm run_loop_test_file`
- Append `-- --nocapture` to surface per-test diagnostics when a failure occurs.
- Individual generated tests follow the pattern `run_vm_test_file_tests_rvm_vm_suites_<suite>_yaml`, so you can use that fragment with `cargo test` to run a single suite.
## Layout
- `suites/*.yaml` — primary instruction, control-flow, and integration suites.
- `suites/loops/*.yaml` — dedicated loop/comprehension suites.
- Mirror the comment headers inside each suite when adding new files; the descriptions are surfaced in this README for quick reference.
## Main Suites (`suites/*.yaml`)
| Suite | Focus |
| --- | --- |
| `arithmetic_operations.yaml` | Arithmetic opcodes (`Add`, `Sub`, `Mul`, `Div`, `Mod`) and simple expressions. |
| `assertions.yaml` | `AssertCondition` semantics, including success/failure and loop control interactions. |
| `basic_instructions.yaml` | Core load/move/return instructions that underpin every program. |
| `boolean_literals.yaml` | `LoadBool`, `LoadTrue`, `LoadFalse`, and their interaction with logical operators. |
| `builtin_functions.yaml` | Builtin dispatch covering argument marshalling, return handling, and error cases. |
| `call_rule.yaml` | `CallRule` execution, rule caches, defaults, and fallbacks. |
| `comparison_operations.yaml` | Relational operators plus logical combining (`Eq`, `Ne`, `Lt`, `Le`, `Gt`, `Ge`, `And`, `Or`, `Not`). |
| `complex.yaml` | Deeply nested hybrid loops, comprehensions, and rule calls that stress the scheduler. |
| `constructed_collections.yaml` | `ArrayCreate`/`SetCreate` success paths, undefined propagation, and deduplication. |
| `control_flow.yaml` | Conditional branching patterns, nested assertions, and selection logic. |
| `core_semantics.yaml` | Broad regression coverage for arithmetic, comparisons, loops, assertions, and collection helpers. |
| `data_structures.yaml` | Array/object/set creation, access, and mutation instructions. |
| `deep_nesting.yaml` | Three-plus levels of mixed loop modes validating register pressure and control flow correctness. |
| `default_rules.yaml` | Complete rule execution with default literals and failure fallbacks. |
| `destructuring_rules.yaml` | Destructuring metadata handling, success/early-exit semantics. |
| `function_calls.yaml` | User function invocation plumbing, argument passing, and returns. |
| `halt.yaml` | `Halt` instruction returning register `0` and stopping execution. |
| `host_await.yaml` | Successful `HostAwait` responses across execution modes and run-to-completion flows. |
| `host_await_failures.yaml` | Error signalling and ignore-flag behaviour for `HostAwait`. |
| `indexed_access.yaml` | Literal/register indexing, chained accesses, and undefined propagation. |
| `integration_scenarios.yaml` | Real-world policy shapes (RBAC, filtering, transforms, workflows). |
| `interpreter_operator_compatibility.yaml` | Ensures VM operators match interpreter behaviour on edge cases. |
| `invalid_collection_ops.yaml` | Error paths for object/set/array mutations with incorrect types. |
| `load_data_input.yaml` | `LoadData` and `LoadInput` instructions across nested/empty/undefined sources. |
| `loop_invalid_iteration.yaml` | Loop errors for non-iterables plus instruction-limit enforcement. |
| `null_undefined_handling.yaml` | Null/undefined behaviour across arithmetic, comparisons, indexing, loops, and comprehensions. |
| `object_operations.yaml` | Advanced object templates, dynamic keys, collisions, and validation. |
| `predefined.yaml` | Global `data` and `input` bindings, including nested access patterns. |
| `resource_limits.yaml` | Instruction counts, recursion depth, and other resource exhaustion scenarios. |
| `serialization.yaml` | Round-trip binary serialization for compiled programs covering all instruction families. |
| `set_operations.yaml` | Set creation, deduplication, membership checks, and nested values. |
| `type_errors.yaml` | Graceful error reporting for cross-family type mismatches. |
| `virtual_data_lookup.yaml` | `VirtualDataDocumentLookup` with base data, rule overrides, and invalid indices. |
## Loop Suites (`suites/loops/*.yaml`)
| Suite | Focus |
| --- | --- |
| `array_comprehensions.yaml` | Mapping, filtering, and edge cases for array comprehensions. |
| `empty.yaml` | Behaviour of every loop mode over empty collections (vacuous truth/falsehood). |
| `existential.yaml` | `some`-style (`Any`) quantification including early exits and complex predicates. |
| `loop_comprehension_interactions.yaml` | Interplay between nested loops and comprehensions emitting structured data. |
| `nested.yaml` | Mixed nesting patterns for loops and comprehensions with varying depth. |
| `nested_fixed.yaml` | Placeholder for future fixed-nesting scenarios (no cases yet). |
| `object_comprehensions.yaml` | Key/value emission, collision handling, and filtering in object comprehensions. |
| `set_comprehensions.yaml` | Deduplication and uniqueness guarantees in set comprehensions. |
| `universal.yaml` | `every`-style (`Every`) quantification, early failure, and vacuous truth cases. |
## Adding or Updating Suites
1. Place the new YAML file under `suites/` (or the relevant `suites/loops/` subdirectory).
2. Add a concise comment block at the top describing the intent and scenarios.
3. Update the tables above so the catalog stays accurate.
4. Run `cargo test --features rvm run_vm_test_file` to ensure the suite loads and all cases pass.
Keeping this README current makes it easier to discover coverage gaps and reason about the generated tests.

View File

@@ -0,0 +1,168 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Arithmetic Operations Test Suite
# Tests mathematical operations: Add, Sub, Mul, Div
# These instructions perform basic arithmetic on numeric values
cases:
- note: arithmetic_add
description: Test Add instruction
example_rego: "10 + 5" # Addition expression
literals:
- 10
- 5
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load literal 10 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load literal 5 into register 1
- "Add { dest: 2, left: 0, right: 1 }" # Add register 0 + register 1, store in register 2
- "Return { value: 2 }" # Return result from register 2
want_result: 15
- note: arithmetic_sub
description: Test Sub instruction
example_rego: "10 - 3" # Subtraction expression
literals:
- 10
- 3
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load literal 10 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load literal 3 into register 1
- "Sub { dest: 2, left: 0, right: 1 }" # Subtract register 1 from register 0, store in register 2
- "Return { value: 2 }" # Return result from register 2
want_result: 7
- note: arithmetic_mul
description: Test Mul instruction
example_rego: "4 * 6" # Multiplication expression
literals:
- 4
- 6
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load literal 4 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load literal 6 into register 1
- "Mul { dest: 2, left: 0, right: 1 }" # Multiply register 0 * register 1, store in register 2
- "Return { value: 2 }" # Return result from register 2
want_result: 24
- note: arithmetic_div
description: Test Div instruction
example_rego: "15 / 3" # Division expression
literals:
- 15
- 3
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load literal 15 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load literal 3 into register 1
- "Div { dest: 2, left: 0, right: 1 }" # Divide register 0 / register 1, store in register 2
- "Return { value: 2 }" # Return result from register 2
want_result: 5
- note: arithmetic_div_by_zero
description: Test Div by zero - undefined normally, error in strict mode
example_rego: "10 / 0"
literals:
- 10
- 0
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Div { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: "#undefined"
want_error_strict: "Cannot divide"
- note: arithmetic_mod
description: Test Mod instruction
example_rego: "10 % 3"
literals:
- 10
- 3
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Mod { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: 1
- note: arithmetic_mod_by_zero
description: Test Mod by zero - undefined normally, error in strict mode
example_rego: "10 % 0"
literals:
- 10
- 0
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Mod { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: "#undefined"
want_error_strict: "Cannot modulo"
- note: arithmetic_mod_negative_operands
description: Test Mod with negative operands
example_rego: "-10 % 3"
literals:
- -10
- 3
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Mod { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: -1
- note: arithmetic_mod_on_float_error
description: Test Mod on float - should error
example_rego: "10.5 % 3"
literals:
- 10.5
- 3
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Mod { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_error: "modulo on floating-point number"
- note: arithmetic_chained_operations
description: Test chained arithmetic operations (a + b) * c
example_rego: "(5 + 3) * 2"
literals:
- 5
- 3
- 2
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load 5
- "Load { dest: 1, literal_idx: 1 }" # Load 3
- "Add { dest: 2, left: 0, right: 1 }" # 5 + 3 = 8
- "Load { dest: 3, literal_idx: 2 }" # Load 2
- "Mul { dest: 4, left: 2, right: 3 }" # 8 * 2 = 16
- "Return { value: 4 }"
want_result: 16
- note: arithmetic_float_precision
description: Test float arithmetic precision
example_rego: "0.1 + 0.2"
literals:
- 0.1
- 0.2
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Add { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: 0.3
- note: arithmetic_large_numbers
description: Test arithmetic with large numbers
example_rego: "1000000 * 1000000"
literals:
- 1000000
- 1000000
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Mul { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: 1000000000000

View File

@@ -0,0 +1,47 @@
# Assertions Test Suite
# Exercises AssertNotUndefined behavior in isolation and inside loops.
cases:
- note: assert_not_undefined_passes
description: AssertNotUndefined succeeds when register has a value
literals:
- 42
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "AssertNotUndefined { register: 0 }"
- "Return { value: 0 }"
want_result: 42
- note: assert_not_undefined_fails
description: AssertNotUndefined triggers assertion error when register undefined
literals:
- "#undefined"
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "AssertNotUndefined { register: 0 }"
- "Return { value: 0 }"
want_error: "Assertion failed"
- note: assert_not_undefined_inside_loop
description: AssertNotUndefined fails inside Every loop and exits with false result
literals:
- "#undefined"
instruction_params:
loop_params:
- mode: "Every"
collection: 0
key_reg: 1
value_reg: 2
result_reg: 3
body_start: 5
loop_end: 7
instructions:
- "ArrayNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "ArrayPush { arr: 0, value: 1 }"
- "LoopStart { params_index: 0 }"
- "Load { dest: 2, literal_idx: 0 }"
- "AssertNotUndefined { register: 2 }"
- "LoopNext { body_start: 5, loop_end: 7 }"
- "Return { value: 3 }"
want_result: false

View File

@@ -0,0 +1,32 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Basic VM Instructions Test Suite
# Tests fundamental instructions: Load, Move, Return
# These form the foundation for all other VM operations
cases:
- note: load_instruction
description: Test basic Load instruction
example_rego: "42" # Simple literal expression
literals:
- 42
- "hello"
- true
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load literal 42 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load literal "hello" into register 1
- "Load { dest: 2, literal_idx: 2 }" # Load literal true into register 2
- "Return { value: 0 }" # Return value from register 0
want_result: 42
- note: move_instruction
description: Test Move instruction
example_rego: "x := 123; x" # Variable assignment and reference
literals:
- 123
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load literal 123 into register 0 (x := 123)
- "Move { dest: 1, src: 0 }" # Move value from register 0 to register 1 (reference x)
- "Return { value: 1 }" # Return value from register 1
want_result: 123

View File

@@ -0,0 +1,44 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Boolean Literal Instructions Test Suite
# Validates that LoadBool behaves consistently with LoadTrue/LoadFalse
# and integrates correctly with logical instructions.
cases:
- note: load_bool_true
description: LoadBool true matches LoadTrue semantics
literals: []
instructions:
- "LoadBool { dest: 0, value: true }"
- "Return { value: 0 }"
want_result: true
- note: load_bool_false
description: LoadBool false matches LoadFalse semantics
literals: []
instructions:
- "LoadBool { dest: 0, value: false }"
- "Return { value: 0 }"
want_result: false
- note: load_bool_with_and
description: Combine LoadBool values using And instruction
literals: []
instructions:
- "LoadBool { dest: 0, value: true }"
- "LoadBool { dest: 1, value: false }"
- "And { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: false
- note: load_bool_with_or_not
description: Ensure LoadBool integrates with Or and Not
literals: []
instructions:
- "LoadBool { dest: 0, value: false }"
- "LoadBool { dest: 1, value: true }"
- "Or { dest: 2, left: 0, right: 1 }"
- "Not { dest: 3, operand: 2 }"
- "Return { value: 3 }"
want_result: false

View File

@@ -0,0 +1,524 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Builtin Functions Test Suite
# Tests the VM's builtin function call mechanism with various builtin functions
# Covers argument handling, return values, and error cases
cases:
# Basic Count Function Tests
- note: builtin_count_array
description: Test count builtin with array argument
example_rego: "count([1, 2, 3])"
literals:
- [1, 2, 3]
instruction_params:
builtin_infos:
- name: "count"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load [1, 2, 3] into register 0
- "BuiltinCall { params_index: 0 }" # Call count(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: 3
- note: builtin_count_string
description: Test count builtin with string argument
example_rego: "count(\"hello\")"
literals:
- "hello"
instruction_params:
builtin_infos:
- name: "count"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load "hello" into register 0
- "BuiltinCall { params_index: 0 }" # Call count(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: 5
- note: builtin_count_object
description: Test count builtin with object argument
example_rego: "count({\"a\": 1, \"b\": 2})"
literals:
- {"a": 1, "b": 2}
instruction_params:
builtin_infos:
- name: "count"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load object into register 0
- "BuiltinCall { params_index: 0 }" # Call count(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: 2
- note: builtin_count_empty_array
description: Test count builtin with empty array
example_rego: "count([])"
literals:
- []
instruction_params:
builtin_infos:
- name: "count"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load empty array into register 0
- "BuiltinCall { params_index: 0 }" # Call count(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: 0
# Max Function Tests
- note: builtin_max_array
description: Test max builtin with array argument
example_rego: "max([1, 5, 3])"
literals:
- [1, 5, 3]
instruction_params:
builtin_infos:
- name: "max"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load [1, 5, 3] into register 0
- "BuiltinCall { params_index: 0 }" # Call max(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: 5
- note: builtin_max_empty_array
description: Test max builtin with empty array returns undefined
example_rego: "max([])"
literals:
- []
instruction_params:
builtin_infos:
- name: "max"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load empty array into register 0
- "BuiltinCall { params_index: 0 }" # Call max(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: "#undefined"
# Min Function Tests
- note: builtin_min_array
description: Test min builtin with array argument
example_rego: "min([1, 5, 3])"
literals:
- [1, 5, 3]
instruction_params:
builtin_infos:
- name: "min"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load [1, 5, 3] into register 0
- "BuiltinCall { params_index: 0 }" # Call min(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: 1
# Sum Function Tests
- note: builtin_sum_array
description: Test sum builtin with numeric array
example_rego: "sum([1, 2, 3, 4])"
literals:
- [1, 2, 3, 4]
instruction_params:
builtin_infos:
- name: "sum"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load [1, 2, 3, 4] into register 0
- "BuiltinCall { params_index: 0 }" # Call sum(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: 10
- note: builtin_sum_empty_array
description: Test sum builtin with empty array
example_rego: "sum([])"
literals:
- []
instruction_params:
builtin_infos:
- name: "sum"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load empty array into register 0
- "BuiltinCall { params_index: 0 }" # Call sum(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: 0
# String Functions Tests
- note: builtin_upper_string
description: Test upper builtin function
example_rego: "upper(\"hello\")"
literals:
- "hello"
instruction_params:
builtin_infos:
- name: "upper"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load "hello" into register 0
- "BuiltinCall { params_index: 0 }" # Call upper(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: "HELLO"
- note: builtin_lower_string
description: Test lower builtin function
example_rego: "lower(\"WORLD\")"
literals:
- "WORLD"
instruction_params:
builtin_infos:
- name: "lower"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load "WORLD" into register 0
- "BuiltinCall { params_index: 0 }" # Call lower(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: "world"
# Multi-argument Builtin Tests
- note: builtin_contains_string
description: Test contains builtin with string arguments
example_rego: "contains(\"hello world\", \"world\")"
literals:
- "hello world"
- "world"
instruction_params:
builtin_infos:
- name: "contains"
num_args: 2
builtin_call_params:
- dest: 2
builtin_index: 0
args: [0, 1]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load "hello world" into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load "world" into register 1
- "BuiltinCall { params_index: 0 }" # Call contains(register[0], register[1]), result in register 2
- "Return { value: 2 }" # Return result
want_result: true
- note: builtin_contains_string_false
description: Test contains builtin with non-matching strings
example_rego: "contains(\"hello\", \"world\")"
literals:
- "hello"
- "world"
instruction_params:
builtin_infos:
- name: "contains"
num_args: 2
builtin_call_params:
- dest: 2
builtin_index: 0
args: [0, 1]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load "hello" into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load "world" into register 1
- "BuiltinCall { params_index: 0 }" # Call contains(register[0], register[1]), result in register 2
- "Return { value: 2 }" # Return result
want_result: false
# Array Function Tests
- note: builtin_sort_array
description: Test sort builtin with array
example_rego: "sort([3, 1, 4, 1, 5])"
literals:
- [3, 1, 4, 1, 5]
instruction_params:
builtin_infos:
- name: "sort"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load unsorted array into register 0
- "BuiltinCall { params_index: 0 }" # Call sort(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: [1, 1, 3, 4, 5]
# Type Function Tests
- note: builtin_type_string
description: Test type_name builtin with string
example_rego: "type_name(\"hello\")"
literals:
- "hello"
instruction_params:
builtin_infos:
- name: "type_name"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load "hello" into register 0
- "BuiltinCall { params_index: 0 }" # Call type_name(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: "string"
- note: builtin_type_number
description: Test type_name builtin with number
example_rego: "type_name(42)"
literals:
- 42
instruction_params:
builtin_infos:
- name: "type_name"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load 42 into register 0
- "BuiltinCall { params_index: 0 }" # Call type_name(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: "number"
- note: builtin_type_array
description: Test type_name builtin with array
example_rego: "type_name([1, 2, 3])"
literals:
- [1, 2, 3]
instruction_params:
builtin_infos:
- name: "type_name"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load array into register 0
- "BuiltinCall { params_index: 0 }" # Call type_name(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: "array"
# Complex Builtin Chain Tests
- note: builtin_chained_operations
description: Test chaining multiple builtin calls
example_rego: "upper(lower(\"HELLO\"))"
literals:
- "HELLO"
instruction_params:
builtin_infos:
- name: "lower"
num_args: 1
- name: "upper"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
- dest: 2
builtin_index: 1
args: [1]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load "HELLO" into register 0
- "BuiltinCall { params_index: 0 }" # Call lower(register[0]), result in register 1
- "BuiltinCall { params_index: 1 }" # Call upper(register[1]), result in register 2
- "Return { value: 2 }" # Return final result
want_result: "HELLO"
# Number Function Tests
- note: builtin_abs_positive
description: Test abs builtin with positive number
example_rego: "abs(42)"
literals:
- 42
instruction_params:
builtin_infos:
- name: "abs"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load 42 into register 0
- "BuiltinCall { params_index: 0 }" # Call abs(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: 42
- note: builtin_abs_negative
description: Test abs builtin with negative number
example_rego: "abs(-42)"
literals:
- -42
instruction_params:
builtin_infos:
- name: "abs"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load -42 into register 0
- "BuiltinCall { params_index: 0 }" # Call abs(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: 42
# Set Function Tests - Commented out due to set literal parsing issues
# TODO: Add proper set tests once set literal parsing is fixed
# - note: builtin_union_sets
# description: Test union builtin with sets
# ...
# Error Handling Tests
- note: builtin_missing_function
description: Test calling non-existent builtin function
example_rego: "nonexistent_function(42)"
literals:
- 42
instruction_params:
builtin_infos:
- name: "nonexistent_function"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load 42 into register 0
- "BuiltinCall { params_index: 0 }" # Call nonexistent_function(register[0]), should error
- "Return { value: 1 }" # Return result
want_error: "Missing builtin function: nonexistent_function"
# Test sets and union builtin
- note: "union([{1, 2}, {2, 3}]) should return {1, 2, 3}"
description: Test union builtin with set of sets
example_rego: "union([{1, 2}, {2, 3}])"
literals:
-
set!:
-
set!:
- 1
- 2
-
set!:
- 2
- 3
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "BuiltinCall { params_index: 0 }"
- "Return { value: 1 }"
instruction_params:
builtin_infos:
- name: "union"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
want_result:
set!:
- 1
- 2
- 3
# Boolean Functions
- note: builtin_is_boolean_true
description: Test is_boolean builtin with true value
example_rego: "is_boolean(true)"
literals:
- true
instruction_params:
builtin_infos:
- name: "is_boolean"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load true into register 0
- "BuiltinCall { params_index: 0 }" # Call is_boolean(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: true
- note: builtin_is_boolean_false
description: Test is_boolean builtin with false value
example_rego: "is_boolean(false)"
literals:
- false
instruction_params:
builtin_infos:
- name: "is_boolean"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load false into register 0
- "BuiltinCall { params_index: 0 }" # Call is_boolean(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: true
- note: builtin_is_boolean_number
description: Test is_boolean builtin with number value (should return false)
example_rego: "is_boolean(42)"
literals:
- 42
instruction_params:
builtin_infos:
- name: "is_boolean"
num_args: 1
builtin_call_params:
- dest: 1
builtin_index: 0
args: [0]
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load 42 into register 0
- "BuiltinCall { params_index: 0 }" # Call is_boolean(register[0]), result in register 1
- "Return { value: 1 }" # Return result
want_result: false

View File

@@ -0,0 +1,76 @@
# CallRule Test Suite
# Validates rule caching, partial structures, defaults, and inconsistencies.
cases:
- note: call_rule_basic_cache
description: CallRule caches results after first evaluation
literals:
- {}
- 1
rule_infos:
- rule_type: Complete
definitions:
- [3]
rule_tree:
data:
test:
allow: 0
instructions:
- "CallRule { dest: 0, rule_index: 0 }"
- "CallRule { dest: 2, rule_index: 0 }"
- "Return { value: 2 }"
- "RuleInit { result_reg: 1, rule_index: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "RuleReturn {}"
want_result: 1
- note: call_rule_partial_object_default
description: Partial object rule returns object even when no fields defined
literals:
- {}
rule_infos:
- rule_type: PartialObject
definitions:
- [2]
instructions:
- "CallRule { dest: 0, rule_index: 0 }"
- "Return { value: 0 }"
- "RuleInit { result_reg: 1, rule_index: 0 }"
- "RuleReturn {}"
want_result: {}
- note: call_rule_default_literal
description: Default literal used when complete rule returns undefined
literals:
- "default"
rule_infos:
- rule_type: Complete
definitions:
- [2]
default_literal_index: 0
instructions:
- "CallRule { dest: 0, rule_index: 0 }"
- "Return { value: 0 }"
- "RuleInit { result_reg: 1, rule_index: 0 }"
- "RuleReturn {}"
want_result: "default"
- note: call_rule_inconsistency
description: Complete rule with inconsistent results returns undefined
literals:
- 1
- 2
rule_infos:
- rule_type: Complete
definitions:
- [2, 5]
instructions:
- "CallRule { dest: 0, rule_index: 0 }"
- "Return { value: 0 }"
- "RuleInit { result_reg: 1, rule_index: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "RuleReturn {}"
- "RuleInit { result_reg: 1, rule_index: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "RuleReturn {}"
want_result: "#undefined"

View File

@@ -0,0 +1,111 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Comparison Operations Test Suite
# Tests comparison instructions: Eq, Ne, Lt, Le, Gt, Ge
# These instructions compare values and produce boolean results
cases:
- note: comparison_eq
description: Test Eq instruction
example_rego: "5 == 5" # Equality comparison
literals:
- 5
- 5
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load literal 5 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load literal 5 into register 1
- "Eq { dest: 2, left: 0, right: 1 }" # Compare register 0 == register 1, store result in register 2
- "Return { value: 2 }" # Return boolean result from register 2
want_result: true
- note: comparison_eq_false
description: Test Eq instruction with false result
example_rego: "5 == 3" # Equality comparison (false case)
literals:
- 5
- 3
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load literal 5 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load literal 3 into register 1
- "Eq { dest: 2, left: 0, right: 1 }" # Compare register 0 == register 1, store result in register 2
- "Return { value: 2 }" # Return boolean result from register 2
want_result: false
- note: comparison_ne
description: Test Ne instruction
example_rego: "5 != 3" # Inequality comparison
literals:
- 5
- 3
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load literal 5 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load literal 3 into register 1
- "Ne { dest: 2, left: 0, right: 1 }" # Compare register 0 != register 1, store result in register 2
- "Return { value: 2 }" # Return boolean result from register 2
want_result: true
- note: comparison_lt
description: Test Lt instruction
example_rego: "3 < 5" # Less than comparison
literals:
- 3
- 5
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load literal 3 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load literal 5 into register 1
- "Lt { dest: 2, left: 0, right: 1 }" # Compare register 0 < register 1, store result in register 2
- "Return { value: 2 }" # Return boolean result from register 2
want_result: true
- note: comparison_le
description: Test Le instruction (less than or equal)
example_rego: "5 <= 10" # Less than or equal comparison
literals:
- 5
- 10
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load literal 5 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load literal 10 into register 1
- "Le { dest: 2, left: 0, right: 1 }" # Compare register 0 <= register 1, store result in register 2
- "Return { value: 2 }" # Return boolean result from register 2
want_result: true
- note: comparison_ge
description: Test Ge instruction (greater than or equal)
example_rego: "10 >= 5" # Greater than or equal comparison
literals:
- 10
- 5
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load literal 10 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load literal 5 into register 1
- "Ge { dest: 2, left: 0, right: 1 }" # Compare register 0 >= register 1, store result in register 2
- "Return { value: 2 }" # Return boolean result from register 2
want_result: true
- note: comparison_gt
description: Test Gt instruction
example_rego: "7 > 3" # Greater than comparison
literals:
- 7
- 3
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load literal 7 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load literal 3 into register 1
- "Gt { dest: 2, left: 0, right: 1 }" # Compare register 0 > register 1, store result in register 2
- "Return { value: 2 }" # Return boolean result from register 2
want_result: true
- note: comparison_ge_equal
description: Test Ge instruction with equal values
example_rego: "5 >= 5" # Greater than or equal comparison (equal case)
literals:
- 5
- 5
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load literal 5 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load literal 5 into register 1
- "Ge { dest: 2, left: 0, right: 1 }" # Compare register 0 >= register 1, store result in register 2
- "Return { value: 2 }" # Return boolean result from register 2
want_result: true

View File

@@ -0,0 +1,230 @@
# Complex VM Control Flow Suite
# Exercises deep nesting of heterogeneous loops, comprehensions, and rule calls.
cases:
- note: twenty_level_loop_maze
description: Twenty levels of mixed loop modes with embedded comprehension and rule call
literals:
- 0
- 1
instruction_params:
loop_params:
- mode: "ForEach"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 10
body_start: 5
loop_end: 55
- mode: "Any"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 11
body_start: 6
loop_end: 54
- mode: "Every"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 12
body_start: 7
loop_end: 53
- mode: "ForEach"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 13
body_start: 8
loop_end: 52
- mode: "Any"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 14
body_start: 9
loop_end: 51
- mode: "Every"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 15
body_start: 10
loop_end: 50
- mode: "ForEach"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 16
body_start: 11
loop_end: 49
- mode: "Any"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 17
body_start: 12
loop_end: 48
- mode: "Every"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 18
body_start: 13
loop_end: 47
- mode: "ForEach"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 19
body_start: 14
loop_end: 46
- mode: "Any"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 20
body_start: 15
loop_end: 45
- mode: "Every"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 21
body_start: 16
loop_end: 44
- mode: "ForEach"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 22
body_start: 17
loop_end: 43
- mode: "Any"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 23
body_start: 18
loop_end: 42
- mode: "Every"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 24
body_start: 19
loop_end: 41
- mode: "ForEach"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 25
body_start: 20
loop_end: 40
- mode: "Any"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 26
body_start: 21
loop_end: 39
- mode: "Every"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 27
body_start: 22
loop_end: 38
- mode: "ForEach"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 28
body_start: 23
loop_end: 37
- mode: "Any"
collection: 1
key_reg: 2
value_reg: 3
result_reg: 29
body_start: 24
loop_end: 36
- mode: "ForEach"
collection: 4
key_reg: 30
value_reg: 31
result_reg: 32
body_start: 28
loop_end: 31
comprehension_begin_params:
- mode: "Array"
collection_reg: 4
key_reg: 30
value_reg: 31
body_start: 28
comprehension_end: 31
rule_infos:
- rule_type: Complete
definitions:
- [56]
instructions:
- "ArrayNew { dest: 1 }"
- "Load { dest: 8, literal_idx: 1 }"
- "ArrayPush { arr: 1, value: 8 }"
- "Load { dest: 7, literal_idx: 0 }"
- "LoopStart { params_index: 0 }"
- "LoopStart { params_index: 1 }"
- "LoopStart { params_index: 2 }"
- "LoopStart { params_index: 3 }"
- "LoopStart { params_index: 4 }"
- "LoopStart { params_index: 5 }"
- "LoopStart { params_index: 6 }"
- "LoopStart { params_index: 7 }"
- "LoopStart { params_index: 8 }"
- "LoopStart { params_index: 9 }"
- "LoopStart { params_index: 10 }"
- "LoopStart { params_index: 11 }"
- "LoopStart { params_index: 12 }"
- "LoopStart { params_index: 13 }"
- "LoopStart { params_index: 14 }"
- "LoopStart { params_index: 15 }"
- "LoopStart { params_index: 16 }"
- "LoopStart { params_index: 17 }"
- "LoopStart { params_index: 18 }"
- "LoopStart { params_index: 19 }"
- "ArrayNew { dest: 4 }"
- "ArrayPush { arr: 4, value: 8 }"
- "ComprehensionBegin { params_index: 0 }"
- "LoopStart { params_index: 20 }"
- "Move { dest: 31, src: 8 }"
- "ComprehensionYield { value_reg: 31 }"
- "LoopNext { body_start: 28, loop_end: 31 }"
- "ComprehensionEnd"
- "Add { dest: 7, left: 7, right: 8 }"
- "CallRule { dest: 9, rule_index: 0 }"
- "Add { dest: 7, left: 7, right: 9 }"
- "LoopNext { body_start: 24, loop_end: 36 }"
- "LoopNext { body_start: 23, loop_end: 37 }"
- "LoopNext { body_start: 22, loop_end: 38 }"
- "LoopNext { body_start: 21, loop_end: 39 }"
- "LoopNext { body_start: 20, loop_end: 40 }"
- "LoopNext { body_start: 19, loop_end: 41 }"
- "LoopNext { body_start: 18, loop_end: 42 }"
- "LoopNext { body_start: 17, loop_end: 43 }"
- "LoopNext { body_start: 16, loop_end: 44 }"
- "LoopNext { body_start: 15, loop_end: 45 }"
- "LoopNext { body_start: 14, loop_end: 46 }"
- "LoopNext { body_start: 13, loop_end: 47 }"
- "LoopNext { body_start: 12, loop_end: 48 }"
- "LoopNext { body_start: 11, loop_end: 49 }"
- "LoopNext { body_start: 10, loop_end: 50 }"
- "LoopNext { body_start: 9, loop_end: 51 }"
- "LoopNext { body_start: 8, loop_end: 52 }"
- "LoopNext { body_start: 7, loop_end: 53 }"
- "LoopNext { body_start: 6, loop_end: 54 }"
- "LoopNext { body_start: 5, loop_end: 55 }"
- "Return { value: 7 }"
- "RuleInit { result_reg: 1, rule_index: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "RuleReturn {}"
want_result: 2

View File

@@ -0,0 +1,80 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Constructed Collections Test Suite
# Validates ArrayCreate and SetCreate behavior including undefined handling.
cases:
- note: array_create_basic
description: ArrayCreate assembles elements from registers
literals:
- 1
- 2
instruction_params:
array_create_params:
- dest: 3
elements: [0, 1, 2]
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Add { dest: 2, left: 0, right: 1 }"
- "ArrayCreate { params_index: 0 }"
- "Return { value: 3 }"
want_result:
- 1
- 2
- 3
- note: array_create_with_undefined
description: ArrayCreate returns undefined if any source is undefined
literals:
- 1
- {}
- "missing"
instruction_params:
array_create_params:
- dest: 2
elements: [0, 1]
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 3, literal_idx: 1 }"
- "Load { dest: 4, literal_idx: 2 }"
- "Index { dest: 1, container: 3, key: 4 }"
- "ArrayCreate { params_index: 0 }"
- "Return { value: 2 }"
want_result: "#undefined"
- note: set_create_basic
description: SetCreate deduplicates values and collects registers
literals:
- 1
- 2
instruction_params:
set_create_params:
- dest: 3
elements: [0, 1, 2]
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Load { dest: 2, literal_idx: 0 }"
- "SetCreate { params_index: 0 }"
- "Return { value: 3 }"
want_result:
set!: [1, 2]
- note: set_create_with_undefined
description: SetCreate returns undefined if any source register is undefined
literals:
- {}
- "missing"
instruction_params:
set_create_params:
- dest: 3
elements: [0]
instructions:
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Index { dest: 0, container: 1, key: 2 }"
- "SetCreate { params_index: 0 }"
- "Return { value: 3 }"
want_result: "#undefined"

View File

@@ -0,0 +1,115 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Control Flow Test Suite
# Tests conditional execution and branching patterns
# Focuses on AssertCondition and conditional logic without loops
cases:
- note: assert_condition_true
description: Test AssertCondition with true condition
example_rego: "x > 5; x := 10" # Simple condition that should succeed
literals:
- 10
- 5
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load 10 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load 5 into register 1
- "Gt { dest: 2, left: 0, right: 1 }" # Check if 10 > 5, store result in register 2
- "AssertCondition { condition: 2 }" # Assert the condition (should succeed)
- "Return { value: 0 }" # Return the original value
want_result: 10
- note: assert_condition_false
description: Test AssertCondition with false condition
example_rego: "x > 15; x := 10" # Simple condition that should fail
literals:
- 10
- 15
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load 10 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load 15 into register 1
- "Gt { dest: 2, left: 0, right: 1 }" # Check if 10 > 15, store result in register 2
- "AssertCondition { condition: 2 }" # Assert the condition (should fail)
- "Return { value: 0 }" # Return the original value (never reached)
want_result: "#undefined" # VM should return undefined for failed assertion
- note: complex_condition_and
description: Test complex AND condition
example_rego: "x > 5; x < 15; x := 10" # Multiple conditions (both must be true)
literals:
- 10
- 5
- 15
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load 10 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load 5 into register 1
- "Load { dest: 2, literal_idx: 2 }" # Load 15 into register 2
- "Gt { dest: 3, left: 0, right: 1 }" # Check if 10 > 5, store result in register 3
- "Lt { dest: 4, left: 0, right: 2 }" # Check if 10 < 15, store result in register 4
- "And { dest: 5, left: 3, right: 4 }" # AND both conditions, store result in register 5
- "AssertCondition { condition: 5 }" # Assert the combined condition (should succeed)
- "Return { value: 0 }" # Return the original value
want_result: 10
- note: complex_condition_or
description: Test complex OR condition
example_rego: "x < 5; x > 15; x := 10" # Either condition can be true (both are false here)
literals:
- 10
- 5
- 15
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load 10 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load 5 into register 1
- "Load { dest: 2, literal_idx: 2 }" # Load 15 into register 2
- "Lt { dest: 3, left: 0, right: 1 }" # Check if 10 < 5, store result in register 3
- "Gt { dest: 4, left: 0, right: 2 }" # Check if 10 > 15, store result in register 4
- "Or { dest: 5, left: 3, right: 4 }" # OR both conditions, store result in register 5
- "AssertCondition { condition: 5 }" # Assert the combined condition (should fail)
- "Return { value: 0 }" # Return the original value (never reached)
want_result: "#undefined" # VM should return undefined for failed assertion
- note: conditional_value_selection
description: Test conditional value selection
example_rego: "result := x > 10 ? x * 2 : x * 3; x := 15" # Conditional expression simulation
literals:
- 15
- 10
- 2
- 3
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load 15 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load 10 into register 1
- "Load { dest: 2, literal_idx: 2 }" # Load 2 into register 2
- "Load { dest: 3, literal_idx: 3 }" # Load 3 into register 3
- "Gt { dest: 4, left: 0, right: 1 }" # Check if 15 > 10, store result in register 4
- "Mul { dest: 5, left: 0, right: 2 }" # Compute 15 * 2, store in register 5
- "Mul { dest: 6, left: 0, right: 3 }" # Compute 15 * 3, store in register 6
# Simulate conditional selection (in real VM this would use conditional instructions)
- "AssertCondition { condition: 4 }" # Since condition is true, we proceed with first value
- "Return { value: 5 }" # Return x * 2 (30)
want_result: 30
- note: nested_conditions
description: Test nested conditional logic
example_rego: "x > 0; y > 0; z := x + y; z > 10; x := 8; y := 5" # Nested conditions with intermediate calculation
literals:
- 8
- 5
- 0
- 10
instructions:
- "Load { dest: 0, literal_idx: 0 }" # Load x=8 into register 0
- "Load { dest: 1, literal_idx: 1 }" # Load y=5 into register 1
- "Load { dest: 2, literal_idx: 2 }" # Load 0 into register 2
- "Load { dest: 3, literal_idx: 3 }" # Load 10 into register 3
- "Gt { dest: 4, left: 0, right: 2 }" # Check if x > 0, store result in register 4
- "AssertCondition { condition: 4 }" # Assert x > 0 (should succeed)
- "Gt { dest: 5, left: 1, right: 2 }" # Check if y > 0, store result in register 5
- "AssertCondition { condition: 5 }" # Assert y > 0 (should succeed)
- "Add { dest: 6, left: 0, right: 1 }" # Compute z = x + y, store in register 6
- "Gt { dest: 7, left: 6, right: 3 }" # Check if z > 10, store result in register 7
- "AssertCondition { condition: 7 }" # Assert z > 10 (should succeed: 13 > 10)
- "Return { value: 6 }" # Return z value (13)
want_result: 13

View File

@@ -0,0 +1,232 @@
# Comprehensive VM semantics regression suite
# Covers arithmetic, comparisons, loop modes, assertions, and collection helpers.
cases:
- note: arithmetic_ops
description: Verify primitive arithmetic instructions and array aggregation
literals:
- 8
- 2
instructions:
- "ArrayNew { dest: 5 }"
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Add { dest: 2, left: 0, right: 1 }"
- "ArrayPush { arr: 5, value: 2 }"
- "Sub { dest: 3, left: 0, right: 1 }"
- "ArrayPush { arr: 5, value: 3 }"
- "Mul { dest: 4, left: 3, right: 1 }"
- "ArrayPush { arr: 5, value: 4 }"
- "Div { dest: 6, left: 0, right: 1 }"
- "ArrayPush { arr: 5, value: 6 }"
- "Mod { dest: 7, left: 0, right: 1 }"
- "ArrayPush { arr: 5, value: 7 }"
- "Return { value: 5 }"
want_result: [10, 6, 12, 4, 0]
- note: comparison_and_logic
description: Validate comparison, boolean, and negation instructions
literals:
- 5
- 5
- 3
instructions:
- "ArrayNew { dest: 11 }"
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Load { dest: 2, literal_idx: 2 }"
- "Eq { dest: 3, left: 0, right: 1 }"
- "ArrayPush { arr: 11, value: 3 }"
- "Ne { dest: 4, left: 0, right: 1 }"
- "ArrayPush { arr: 11, value: 4 }"
- "Lt { dest: 5, left: 2, right: 0 }"
- "ArrayPush { arr: 11, value: 5 }"
- "Ge { dest: 6, left: 0, right: 2 }"
- "ArrayPush { arr: 11, value: 6 }"
- "And { dest: 7, left: 3, right: 5 }"
- "ArrayPush { arr: 11, value: 7 }"
- "Not { dest: 8, operand: 4 }"
- "ArrayPush { arr: 11, value: 8 }"
- "Or { dest: 9, left: 4, right: 5 }"
- "ArrayPush { arr: 11, value: 9 }"
- "Return { value: 11 }"
want_result: [true, false, true, true, true, true, true]
- note: loop_any_short_circuit_success
description: LoopMode::Any should exit on first successful iteration
literals:
- 1
- 2
- 3
instruction_params:
loop_params:
- mode: "Any"
collection: 0
key_reg: 6
value_reg: 7
result_reg: 8
body_start: 9
loop_end: 12
instructions:
- "ArrayNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "ArrayPush { arr: 0, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "ArrayPush { arr: 0, value: 2 }"
- "Load { dest: 3, literal_idx: 2 }"
- "ArrayPush { arr: 0, value: 3 }"
- "Load { dest: 4, literal_idx: 1 }"
- "LoopStart { params_index: 0 }"
- "Eq { dest: 5, left: 7, right: 4 }"
- "AssertCondition { condition: 5 }"
- "LoopNext { body_start: 9, loop_end: 11 }"
- "Return { value: 8 }"
want_result: true
- note: loop_any_all_fail
description: LoopMode::Any should remain false when no iteration succeeds
literals:
- 1
- 2
- 3
- 99
instruction_params:
loop_params:
- mode: "Any"
collection: 0
key_reg: 6
value_reg: 7
result_reg: 8
body_start: 9
loop_end: 12
instructions:
- "ArrayNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "ArrayPush { arr: 0, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "ArrayPush { arr: 0, value: 2 }"
- "Load { dest: 3, literal_idx: 2 }"
- "ArrayPush { arr: 0, value: 3 }"
- "Load { dest: 4, literal_idx: 3 }"
- "LoopStart { params_index: 0 }"
- "Eq { dest: 5, left: 7, right: 4 }"
- "AssertCondition { condition: 5 }"
- "LoopNext { body_start: 9, loop_end: 11 }"
- "Return { value: 8 }"
want_result: false
- note: loop_every_exits_on_failure
description: LoopMode::Every stops at the first failed iteration and returns false
literals:
- 1
- 0
- 1
instruction_params:
loop_params:
- mode: "Every"
collection: 0
key_reg: 6
value_reg: 7
result_reg: 8
body_start: 10
loop_end: 14
instructions:
- "ArrayNew { dest: 0 }"
- "ArrayNew { dest: 9 }"
- "Load { dest: 1, literal_idx: 0 }"
- "ArrayPush { arr: 0, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "ArrayPush { arr: 0, value: 2 }"
- "Load { dest: 3, literal_idx: 2 }"
- "ArrayPush { arr: 0, value: 3 }"
- "Load { dest: 4, literal_idx: 0 }"
- "LoopStart { params_index: 0 }"
- "ArrayPush { arr: 9, value: 7 }"
- "Eq { dest: 5, left: 7, right: 4 }"
- "AssertCondition { condition: 5 }"
- "LoopNext { body_start: 10, loop_end: 13 }"
- "ArrayNew { dest: 10 }"
- "ArrayPush { arr: 10, value: 8 }"
- "ArrayPush { arr: 10, value: 9 }"
- "Return { value: 10 }"
want_result:
- false
- [1, 0]
- note: loop_foreach_collects_successes
description: LoopMode::ForEach tracks successful iterations while visiting all elements
literals:
- 0
- 5
instruction_params:
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 6
value_reg: 7
result_reg: 8
body_start: 9
loop_end: 13
instructions:
- "ArrayNew { dest: 0 }"
- "ArrayNew { dest: 9 }"
- "Load { dest: 1, literal_idx: 0 }"
- "ArrayPush { arr: 0, value: 1 }"
- "Load { dest: 2, literal_idx: 0 }"
- "ArrayPush { arr: 0, value: 2 }"
- "Load { dest: 3, literal_idx: 1 }"
- "ArrayPush { arr: 0, value: 3 }"
- "LoopStart { params_index: 0 }"
- "Eq { dest: 4, left: 7, right: 3 }"
- "ArrayPush { arr: 9, value: 4 }"
- "AssertCondition { condition: 4 }"
- "LoopNext { body_start: 9, loop_end: 12 }"
- "ArrayNew { dest: 10 }"
- "ArrayPush { arr: 10, value: 8 }"
- "ArrayPush { arr: 10, value: 9 }"
- "Return { value: 10 }"
want_result:
- true
- [false, false, true]
- note: assert_condition_without_loop_errors
description: AssertCondition outside of a loop reports an assertion failure
literals:
- false
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "AssertCondition { condition: 0 }"
- "Return { value: 0 }"
want_error: "Assertion failed"
- note: contains_and_count_helpers
description: Contains and Count semantics across arrays, sets, and non-collections
literals:
- 1
- 2
- 9
instructions:
- "ArrayNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "ArrayPush { arr: 0, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "ArrayPush { arr: 0, value: 2 }"
- "SetNew { dest: 3 }"
- "SetAdd { set: 3, value: 1 }"
- "SetAdd { set: 3, value: 2 }"
- "Contains { dest: 4, collection: 0, value: 1 }"
- "Contains { dest: 5, collection: 3, value: 2 }"
- "Load { dest: 6, literal_idx: 2 }"
- "Contains { dest: 7, collection: 3, value: 6 }"
- "Count { dest: 8, collection: 0 }"
- "Count { dest: 9, collection: 3 }"
- "Count { dest: 10, collection: 1 }"
- "ArrayNew { dest: 11 }"
- "ArrayPush { arr: 11, value: 4 }"
- "ArrayPush { arr: 11, value: 5 }"
- "ArrayPush { arr: 11, value: 7 }"
- "ArrayPush { arr: 11, value: 8 }"
- "ArrayPush { arr: 11, value: 9 }"
- "ArrayPush { arr: 11, value: 10 }"
- "Return { value: 11 }"
want_result: [true, true, false, 2, 2, "#undefined"]

View File

@@ -0,0 +1,76 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Data Structures Test Suite
# Tests instructions for working with arrays, objects, and sets
# These instructions create and manipulate complex data types
cases:
- note: array_operations
description: Test array creation and access
example_rego: "arr := [1, 2, 3]; arr[0]" # Array creation and indexing
literals:
- 1
- 2
- 3
- 0 # index
instructions:
- "ArrayNew { dest: 0 }" # Create empty array in register 0
- "Load { dest: 1, literal_idx: 0 }" # Load 1 into register 1
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
- "Load { dest: 2, literal_idx: 1 }" # Load 2 into register 2
- "ArrayPush { arr: 0, value: 2 }" # Push 2 to array
- "Load { dest: 3, literal_idx: 2 }" # Load 3 into register 3
- "ArrayPush { arr: 0, value: 3 }" # Push 3 to array
- "Load { dest: 4, literal_idx: 3 }" # Load index 0 into register 4
- "Index { dest: 5, container: 0, key: 4 }" # Access array[0], store in register 5
- "Return { value: 5 }" # Return the indexed value
want_result: 1
- note: object_operations
description: Test object creation and access
example_rego: "obj := {\"key1\": \"value1\", \"key2\": 42}; obj.key1" # Object creation and field access
literals:
- {}
- "key1"
- "value1"
- "key2"
- 42
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
instructions:
- "ObjectCreate { params_index: 0 }" # Create empty object in register 0
- "Load { dest: 1, literal_idx: 1 }" # Load "key1" into register 1
- "Load { dest: 2, literal_idx: 2 }" # Load "value1" into register 2
- "ObjectSet { obj: 0, key: 1, value: 2 }" # Set obj["key1"] = "value1"
- "Load { dest: 3, literal_idx: 3 }" # Load "key2" into register 3
- "Load { dest: 4, literal_idx: 4 }" # Load 42 into register 4
- "ObjectSet { obj: 0, key: 3, value: 4 }" # Set obj["key2"] = 42
- "Load { dest: 5, literal_idx: 1 }" # Load "key1" again for lookup
- "Index { dest: 6, container: 0, key: 5 }" # Access obj["key1"], store in register 6
- "Return { value: 6 }" # Return the field value
want_result: "value1"
- note: set_operations
description: Test set creation and membership
example_rego: "s := {1, 2, 3}; 2 in s" # Set creation and membership test
literals:
- 1
- 2
- 3
instructions:
- "SetNew { dest: 0 }" # Create empty set in register 0
- "Load { dest: 1, literal_idx: 0 }" # Load 1 into register 1
- "SetAdd { set: 0, value: 1 }" # Add 1 to set
- "Load { dest: 2, literal_idx: 1 }" # Load 2 into register 2
- "SetAdd { set: 0, value: 2 }" # Add 2 to set
- "Load { dest: 3, literal_idx: 2 }" # Load 3 into register 3
- "SetAdd { set: 0, value: 3 }" # Add 3 to set
- "Load { dest: 4, literal_idx: 1 }" # Load 2 again for membership check
- "Contains { dest: 5, collection: 0, value: 4 }" # Check if 2 is in set, store result in register 5
- "Return { value: 5 }" # Return boolean membership result
want_result: true

View File

@@ -0,0 +1,278 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Deep Nesting Test Suite
# Tests 3+ levels of nested loops with various loop modes and comprehensions
# Verifies register allocation, stack pressure, and control flow correctness
cases:
- note: three_level_nested_any_every_foreach
description: 3-level nesting - Any inside Every inside ForEach
example_rego: |
[outer |
outer := [1, 2][_];
every mid in [1, 2]; some inner in [1, 2]; inner == mid
]
literals:
- 1
- 2
instruction_params:
comprehension_begin_params:
- mode: "Array"
collection_reg: 0
result_reg: 0
key_reg: 10
value_reg: 11
body_start: 7
comprehension_end: 27
loop_params:
- mode: "ForEach" # Outer loop for comprehension
collection: 5
key_reg: 10
value_reg: 11
result_reg: 12
body_start: 7
loop_end: 27
- mode: "Every" # Middle loop
collection: 8
key_reg: 20
value_reg: 21
result_reg: 22
body_start: 13
loop_end: 24
- mode: "Any" # Inner loop
collection: 13
key_reg: 30
value_reg: 31
result_reg: 32
body_start: 19
loop_end: 22
instructions:
# Build outer collection [1, 2]
- "ArrayNew { dest: 5 }"
- "Load { dest: 1, literal_idx: 0 }"
- "ArrayPush { arr: 5, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "ArrayPush { arr: 5, value: 2 }"
- "ComprehensionBegin { params_index: 0 }"
- "LoopStart { params_index: 0 }" # ForEach outer
# Build middle collection [1, 2]
- "ArrayNew { dest: 8 }"
- "Load { dest: 3, literal_idx: 0 }"
- "ArrayPush { arr: 8, value: 3 }"
- "Load { dest: 4, literal_idx: 1 }"
- "ArrayPush { arr: 8, value: 4 }"
- "LoopStart { params_index: 1 }" # Every middle
# Build inner collection [1, 2]
- "ArrayNew { dest: 13 }"
- "Load { dest: 6, literal_idx: 0 }"
- "ArrayPush { arr: 13, value: 6 }"
- "Load { dest: 7, literal_idx: 1 }"
- "ArrayPush { arr: 13, value: 7 }"
- "LoopStart { params_index: 2 }" # Any inner
# Check condition: inner == mid
- "Eq { dest: 40, left: 31, right: 21 }"
- "AssertCondition { condition: 40 }"
- "LoopNext { body_start: 19, loop_end: 22 }"
# End Any loop - check result
- "AssertCondition { condition: 32 }"
- "LoopNext { body_start: 13, loop_end: 24 }"
# End Every loop - check result
- "AssertCondition { condition: 22 }"
- "ComprehensionYield { value_reg: 11 }"
- "LoopNext { body_start: 7, loop_end: 27 }"
- "ComprehensionEnd"
- "Return { value: 0 }"
want_result: [1, 2]
- note: four_level_nested_loops
description: 4-level nested loops - stress test for stack depth
example_rego: |
some a in [1]; some b in [2]; some c in [3]; some d in [4]; a + b + c + d == 10
literals:
- 1
- 2
- 3
- 4
- 10
instruction_params:
loop_params:
- mode: "Any"
collection: 0
key_reg: 10
value_reg: 11
result_reg: 12
body_start: 4
loop_end: 29
- mode: "Any"
collection: 2
key_reg: 20
value_reg: 21
result_reg: 22
body_start: 8
loop_end: 27
- mode: "Any"
collection: 4
key_reg: 30
value_reg: 31
result_reg: 32
body_start: 12
loop_end: 25
- mode: "Any"
collection: 6
key_reg: 40
value_reg: 41
result_reg: 42
body_start: 16
loop_end: 23
instructions:
- "ArrayNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "ArrayPush { arr: 0, value: 1 }"
- "LoopStart { params_index: 0 }" # Level 1
- "ArrayNew { dest: 2 }"
- "Load { dest: 3, literal_idx: 1 }"
- "ArrayPush { arr: 2, value: 3 }"
- "LoopStart { params_index: 1 }" # Level 2
- "ArrayNew { dest: 4 }"
- "Load { dest: 5, literal_idx: 2 }"
- "ArrayPush { arr: 4, value: 5 }"
- "LoopStart { params_index: 2 }" # Level 3
- "ArrayNew { dest: 6 }"
- "Load { dest: 7, literal_idx: 3 }"
- "ArrayPush { arr: 6, value: 7 }"
- "LoopStart { params_index: 3 }" # Level 4
# Compute sum: a + b + c + d
- "Add { dest: 43, left: 11, right: 21 }"
- "Add { dest: 44, left: 43, right: 31 }"
- "Add { dest: 45, left: 44, right: 41 }"
- "Load { dest: 46, literal_idx: 4 }"
- "Eq { dest: 47, left: 45, right: 46 }"
- "AssertCondition { condition: 47 }"
- "LoopNext { body_start: 16, loop_end: 23 }"
- "AssertCondition { condition: 42 }"
- "LoopNext { body_start: 12, loop_end: 25 }"
- "AssertCondition { condition: 32 }"
- "LoopNext { body_start: 8, loop_end: 27 }"
- "AssertCondition { condition: 22 }"
- "LoopNext { body_start: 4, loop_end: 29 }"
- "Return { value: 12 }"
want_result: true
- note: comprehension_inside_nested_loop
description: Array comprehension inside 2-level nested loop
example_rego: |
[[y | y := inner[_]] |
outer := [[1, 2], [3, 4]][_];
inner := outer
]
literals:
- 1
- 2
- 3
- 4
instruction_params:
comprehension_begin_params:
- mode: "Array" # Outer comprehension
collection_reg: 0
key_reg: 10
value_reg: 11
body_start: 15
comprehension_end: 23
- mode: "Array" # Inner comprehension
collection_reg: 14
key_reg: 20
value_reg: 21
body_start: 18
comprehension_end: 20
loop_params:
- mode: "ForEach" # Outer loop
collection: 8
key_reg: 10
value_reg: 11
result_reg: 13
body_start: 15
loop_end: 23
- mode: "ForEach" # Inner loop for comprehension
collection: 12
key_reg: 20
value_reg: 21
result_reg: 22
body_start: 18
loop_end: 20
instructions:
# Build outer array [[1, 2], [3, 4]]
- "ArrayNew { dest: 8 }"
- "ArrayNew { dest: 1 }"
- "Load { dest: 2, literal_idx: 0 }"
- "ArrayPush { arr: 1, value: 2 }"
- "Load { dest: 3, literal_idx: 1 }"
- "ArrayPush { arr: 1, value: 3 }"
- "ArrayPush { arr: 8, value: 1 }"
- "ArrayNew { dest: 4 }"
- "Load { dest: 5, literal_idx: 2 }"
- "ArrayPush { arr: 4, value: 5 }"
- "Load { dest: 6, literal_idx: 3 }"
- "ArrayPush { arr: 4, value: 6 }"
- "ArrayPush { arr: 8, value: 4 }"
- "ComprehensionBegin { params_index: 0 }" # Start outer comprehension
- "LoopStart { params_index: 0 }" # ForEach over outer array
- "Move { dest: 12, src: 11 }" # inner := outer
- "ComprehensionBegin { params_index: 1 }" # Start inner comprehension
- "LoopStart { params_index: 1 }" # ForEach over inner array
- "ComprehensionYield { value_reg: 21 }" # Yield y
- "LoopNext { body_start: 18, loop_end: 20 }"
- "ComprehensionEnd" # End inner comprehension
- "ComprehensionYield { value_reg: 14 }" # Yield inner comprehension result
- "LoopNext { body_start: 15, loop_end: 23 }"
- "ComprehensionEnd" # End outer comprehension
- "Return { value: 0 }"
want_result: [[1, 2], [3, 4]]
- note: register_pressure_nested_loops
description: Nested loops with 15+ active registers to test allocation
example_rego: |
some a in [1]; some b in [2]; a + b > 0
literals:
- 1
- 2
- 0
instruction_params:
loop_params:
- mode: "Any"
collection: 0
key_reg: 10
value_reg: 11
result_reg: 12
body_start: 4
loop_end: 19
- mode: "Any"
collection: 2
key_reg: 20
value_reg: 21
result_reg: 22
body_start: 8
loop_end: 17
instructions:
- "ArrayNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "ArrayPush { arr: 0, value: 1 }"
- "LoopStart { params_index: 0 }"
- "ArrayNew { dest: 2 }"
- "Load { dest: 3, literal_idx: 1 }"
- "ArrayPush { arr: 2, value: 3 }"
- "LoopStart { params_index: 1 }"
# Use many registers to create pressure
- "Move { dest: 23, src: 11 }"
- "Move { dest: 24, src: 21 }"
- "Move { dest: 25, src: 23 }"
- "Move { dest: 26, src: 24 }"
- "Add { dest: 27, left: 25, right: 26 }"
- "Load { dest: 28, literal_idx: 2 }"
- "Gt { dest: 29, left: 27, right: 28 }"
- "AssertCondition { condition: 29 }"
- "LoopNext { body_start: 6, loop_end: 14 }"
- "AssertCondition { condition: 22 }"
- "LoopNext { body_start: 3, loop_end: 16 }"
- "Return { value: 12 }"
want_result: true

View File

@@ -0,0 +1,277 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Default Rules Infrastructure Test Suite
# Tests the VM infrastructure support for default rule evaluation
# These tests verify that the VM can properly handle complete rules with default fallbacks
cases:
- note: vm_default_rule_boolean_false
description: Test VM default rule evaluation with boolean false value
example_rego: "default allow := false; allow := true if { false }"
literals:
- false
- true
rule_infos:
- rule_type: "Complete"
definitions: [[2]] # Rule definition starts at instruction 2
default_literal_index: 0 # Points to literal index 0 (false)
instructions:
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
- "Return { value: 0 }" # Return result
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
- "Load { dest: 2, literal_idx: 0 }" # Load false condition
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
- "Load { dest: 3, literal_idx: 1 }" # Load true result
- "Move { dest: 1, src: 3 }" # Move result to result register
- "RuleReturn" # Return from rule
want_result: false # Should return default value when condition fails
- note: vm_default_rule_boolean_true
description: Test VM default rule evaluation with boolean true value
example_rego: "default enabled := true; enabled := false if { false }"
literals:
- true
- false
rule_infos:
- rule_type: "Complete"
definitions: [[2]] # Rule definition starts at instruction 2
default_literal_index: 0 # Points to literal index 0 (true)
instructions:
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
- "Return { value: 0 }" # Return result
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
- "Load { dest: 2, literal_idx: 1 }" # Load false condition
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
- "Load { dest: 3, literal_idx: 1 }" # Load false result
- "Move { dest: 1, src: 3 }" # Move result to result register
- "RuleReturn" # Return from rule
want_result: true # Should return default value when condition fails
- note: vm_default_rule_string_value
description: Test VM default rule evaluation with string value
example_rego: "default message := \"default\"; message := \"success\" if { false }"
literals:
- "default"
- "success"
- false
rule_infos:
- rule_type: "Complete"
definitions: [[2]] # Rule definition starts at instruction 2
default_literal_index: 0 # Points to literal index 0 ("default")
instructions:
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
- "Return { value: 0 }" # Return result
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
- "Load { dest: 2, literal_idx: 2 }" # Load false condition
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
- "Load { dest: 3, literal_idx: 1 }" # Load success result
- "Move { dest: 1, src: 3 }" # Move result to result register
- "RuleReturn" # Return from rule
want_result: "default" # Should return default value when condition fails
- note: vm_default_rule_number_value
description: Test VM default rule evaluation with numeric value
example_rego: "default count := 0; count := 10 if { false }"
literals:
- 0
- 10
- false
rule_infos:
- rule_type: "Complete"
definitions: [[2]] # Rule definition starts at instruction 2
default_literal_index: 0 # Points to literal index 0 (0)
instructions:
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
- "Return { value: 0 }" # Return result
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
- "Load { dest: 2, literal_idx: 2 }" # Load false condition
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
- "Load { dest: 3, literal_idx: 1 }" # Load 10 result
- "Move { dest: 1, src: 3 }" # Move result to result register
- "RuleReturn" # Return from rule
want_result: 0 # Should return default value when condition fails
- note: vm_default_rule_array_value
description: Test VM default rule evaluation with array value
example_rego: "default items := [\"default\"]; items := [\"success\"] if { false }"
literals:
- ["default"]
- ["success"]
- false
rule_infos:
- rule_type: "Complete"
definitions: [[2]] # Rule definition starts at instruction 2
default_literal_index: 0 # Points to literal index 0 (["default"])
instructions:
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
- "Return { value: 0 }" # Return result
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
- "Load { dest: 2, literal_idx: 2 }" # Load false condition
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
- "Load { dest: 3, literal_idx: 1 }" # Load success array
- "Move { dest: 1, src: 3 }" # Move result to result register
- "RuleReturn" # Return from rule
want_result: ["default"] # Should return default value when condition fails
- note: vm_default_rule_object_value
description: Test VM default rule evaluation with object value
example_rego: "default config := {\"mode\": \"safe\"}; config := {\"mode\": \"fast\"} if { false }"
literals:
- {"mode": "safe"}
- {"mode": "fast"}
- false
rule_infos:
- rule_type: "Complete"
definitions: [[2]] # Rule definition starts at instruction 2
default_literal_index: 0 # Points to literal index 0 ({"mode": "safe"})
instructions:
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
- "Return { value: 0 }" # Return result
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
- "Load { dest: 2, literal_idx: 2 }" # Load false condition
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
- "Load { dest: 3, literal_idx: 1 }" # Load fast config
- "Move { dest: 1, src: 3 }" # Move result to result register
- "RuleReturn" # Return from rule
want_result:
mode: "safe" # Should return default value when condition fails
- note: vm_default_rule_null_value
description: Test VM default rule evaluation with null value
example_rego: "default optional := null; optional := \"value\" if { false }"
literals:
- null
- "value"
- false
rule_infos:
- rule_type: "Complete"
definitions: [[2]] # Rule definition starts at instruction 2
default_literal_index: 0 # Points to literal index 0 (null)
instructions:
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
- "Return { value: 0 }" # Return result
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
- "Load { dest: 2, literal_idx: 2 }" # Load false condition
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
- "Load { dest: 3, literal_idx: 1 }" # Load string value
- "Move { dest: 1, src: 3 }" # Move result to result register
- "RuleReturn" # Return from rule
want_result: null # Should return default value when condition fails
- note: vm_default_rule_multiple_definitions_fail
description: Test VM default rule when multiple definitions all fail
example_rego: "default result := \"fallback\"; result := \"first\" if { false }; result := \"second\" if { false }"
literals:
- "fallback"
- "first"
- "second"
- false
rule_infos:
- rule_type: "Complete"
definitions: [[2], [7]] # Two rule definitions
default_literal_index: 0 # Points to literal index 0 ("fallback")
instructions:
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
- "Return { value: 0 }" # Return result
# First definition
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
- "Load { dest: 2, literal_idx: 3 }" # Load false condition
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
- "Load { dest: 3, literal_idx: 1 }" # Load first result
- "Move { dest: 1, src: 3 }" # Move result to result register
- "RuleReturn" # Return from rule
# Second definition
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
- "Load { dest: 4, literal_idx: 3 }" # Load false condition
- "AssertCondition { condition: 4 }" # Assert condition (will fail)
- "Load { dest: 5, literal_idx: 2 }" # Load second result
- "Move { dest: 1, src: 5 }" # Move result to result register
- "RuleReturn" # Return from rule
want_result: "fallback" # Should return default value when all definitions fail
- note: vm_default_rule_successful_condition
description: Test VM rule evaluation when condition succeeds (should not use default)
example_rego: "default allow := false; allow := true if { true }"
literals:
- false
- true
rule_infos:
- rule_type: "Complete"
definitions: [[2]] # Rule definition starts at instruction 2
default_literal_index: 0 # Points to literal index 0 (false) - should not be used
instructions:
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
- "Return { value: 0 }" # Return result
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
- "Load { dest: 2, literal_idx: 1 }" # Load true condition
- "AssertCondition { condition: 2 }" # Assert condition (will succeed)
- "Load { dest: 3, literal_idx: 1 }" # Load true result
- "Move { dest: 1, src: 3 }" # Move result to result register
- "RuleReturn" # Return from rule
want_result: true # Should return successful rule result, not default
- note: vm_default_rule_complex_object
description: Test VM default rule with complex nested object
example_rego: "default settings := complex_default; settings := complex_success if { false }"
literals:
- {
"timeout": 30,
"retries": 3,
"features": {
"logging": true,
"monitoring": false
},
"endpoints": ["api1", "api2"]
}
- {
"timeout": 60,
"retries": 5,
"features": {
"logging": false,
"monitoring": true
},
"endpoints": ["api3", "api4"]
}
- false
rule_infos:
- rule_type: "Complete"
definitions: [[2]] # Rule definition starts at instruction 2
default_literal_index: 0 # Points to literal index 0 (complex default object)
instructions:
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
- "Return { value: 0 }" # Return result
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
- "Load { dest: 2, literal_idx: 2 }" # Load false condition
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
- "Load { dest: 3, literal_idx: 1 }" # Load success object
- "Move { dest: 1, src: 3 }" # Move result to result register
- "RuleReturn" # Return from rule
want_result:
timeout: 30
retries: 3
features:
logging: true
monitoring: false
endpoints: ["api1", "api2"] # Should return default complex object
- note: vm_no_default_rule_undefined
description: Test VM rule without default returns undefined when definition fails
example_rego: "allow := true if { false } # No default rule"
literals:
- true
- false
rule_infos:
- rule_type: "Complete"
definitions: [[2]] # Rule definition starts at instruction 2
# No default_literal_index - should return undefined
instructions:
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
- "Return { value: 0 }" # Return result
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
- "Load { dest: 2, literal_idx: 1 }" # Load false condition
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
- "Load { dest: 3, literal_idx: 0 }" # Load true result
- "Move { dest: 1, src: 3 }" # Move result to result register
- "RuleReturn" # Return from rule
want_result: "#undefined" # Should return undefined when no default and definition fails

View File

@@ -0,0 +1,41 @@
# Destructuring Rules Test Suite
# Covers DestructuringSuccess control flow for rule parameters.
cases:
- note: destructuring_success_path
description: Destructuring block passes and body executes
literals:
- 1
rule_infos:
- rule_type: Complete
definitions:
- [4]
destructuring_blocks: [2]
instructions:
- "CallRule { dest: 0, rule_index: 0 }"
- "Return { value: 0 }"
- "Load { dest: 2, literal_idx: 0 }"
- "DestructuringSuccess {}"
- "RuleInit { result_reg: 1, rule_index: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "RuleReturn {}"
want_result: 1
- note: destructuring_failure_skips_body
description: Missing DestructuringSuccess causes undefined result
literals:
- 1
rule_infos:
- rule_type: Complete
definitions:
- [4]
destructuring_blocks: [2]
instructions:
- "CallRule { dest: 0, rule_index: 0 }"
- "Return { value: 0 }"
- "LoadFalse { dest: 2 }"
- "AssertCondition { condition: 2 }"
- "RuleInit { result_reg: 1, rule_index: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "RuleReturn {}"
want_result: "#undefined"

View File

@@ -0,0 +1,371 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Function Call Instructions Test Suite
# Tests the VM's function call mechanism with user-defined function rules
# Covers FunctionCall instruction, argument passing, and return values
cases:
# Basic Function Call Tests
- note: simple_function_call
description: Test basic function call instruction with one argument
example_rego: "add_ten(5) where add_ten(x) := x + 10"
literals:
- {}
- 5 # Argument value
- 10 # Constant 10 for addition
instruction_params:
object_create_params:
- dest: 6
template_literal_idx: 0
literal_key_fields: []
fields: []
function_call_params:
- func: 0 # Rule index for add_ten function (rule 0)
dest: 2 # Destination register for result
args: [1] # Argument registers
rule_infos:
- rule_type: Complete
definitions:
- [3] # Entry point for add_ten function body (PC 3)
instructions:
- "Load { dest: 1, literal_idx: 1 }" # Load argument 5 into register 1
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 2 }" # Return result
# Function body starts at PC 3 (entry point)
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "Load { dest: 3, literal_idx: 2 }" # Load constant 10 into register 3
- "Add { dest: 0, left: 1, right: 3 }" # Add argument + 10 (result in register 0)
- "RuleReturn {}" # Return from function
want_result: 15
- note: function_call_multiple_args
description: Test function call with multiple arguments
example_rego: "add(7, 3) where add(x, y) := x + y"
literals:
- {}
- 0 # Rule index for add function
- 7 # First argument
- 3 # Second argument
instruction_params:
function_call_params:
- func: 0 # Register containing function rule index
dest: 3 # Destination register for result
args: [1, 2] # Argument registers
rule_infos:
- rule_type: Complete
definitions:
- [5] # Entry point for add function body
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
- "Load { dest: 1, literal_idx: 2 }" # Load first argument
- "Load { dest: 2, literal_idx: 3 }" # Load second argument
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 3 }" # Return result
# Function body
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "Add { dest: 0, left: 1, right: 2 }" # Add arguments (result in register 0)
- "RuleReturn {}" # Return from function
want_result: 10
- note: function_call_no_args
description: Test function call with no arguments
example_rego: "get_constant() where get_constant() := 42"
literals:
- {}
- 0 # Rule index for get_constant function
- 42 # Constant value
instruction_params:
function_call_params:
- func: 0 # Register containing function rule index
dest: 1 # Destination register for result
args: [] # No arguments
rule_infos:
- rule_type: Complete
definitions:
- [3] # Entry point for get_constant function body
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 1 }" # Return result
# Function body
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "Load { dest: 0, literal_idx: 2 }" # Load constant 42 (result in register 0)
- "RuleReturn {}" # Return from function
want_result: 42
- note: function_call_with_multiplication
description: Test function that performs multiplication
example_rego: "square(6) where square(x) := x * x"
literals:
- {}
- 0 # Rule index for square function
- 6 # Argument value
instruction_params:
function_call_params:
- func: 0 # Register containing function rule index
dest: 2 # Destination register for result
args: [1] # Argument registers
rule_infos:
- rule_type: Complete
definitions:
- [4] # Entry point for square function body
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
- "Load { dest: 1, literal_idx: 2 }" # Load argument 6
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 2 }" # Return result
# Function body
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "Mul { dest: 0, left: 1, right: 1 }" # Multiply x * x (result in register 0)
- "RuleReturn {}" # Return from function
want_result: 36
- note: nested_function_calls
description: Test nested function calls
example_rego: "double(add_one(5)) where double(x) := x * 2; add_one(x) := x + 1"
literals:
- {}
- 1 # Rule index for add_one function
- 0 # Rule index for double function
- 5 # Initial argument
- 1 # Constant 1
- 2 # Constant 2
instruction_params:
function_call_params:
- func: 1 # First call: add_one (rule index 1)
dest: 4 # Temporary result
args: [2] # Argument register
- func: 0 # Second call: double (rule index 0)
dest: 5 # Final result
args: [4] # Use result from first call
rule_infos:
- rule_type: Complete # double function
definitions:
- [10] # Entry point for double function body (updated)
- rule_type: Complete # add_one function
definitions:
- [6] # Entry point for add_one function body
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load add_one rule index
- "Load { dest: 1, literal_idx: 2 }" # Load double rule index
- "Load { dest: 2, literal_idx: 3 }" # Load argument 5
- "FunctionCall { params_index: 0 }" # Call add_one(5)
- "FunctionCall { params_index: 1 }" # Call double(result)
- "Return { value: 5 }" # Return final result
# add_one function body (starts at PC 6)
- "RuleInit { result_reg: 0, rule_index: 1 }" # Initialize result register for add_one
- "Load { dest: 3, literal_idx: 4 }" # Load constant 1
- "Add { dest: 0, left: 1, right: 3 }" # x + 1 (result in register 0)
- "RuleReturn {}" # Return result
# double function body (starts at PC 10)
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register for double
- "Load { dest: 3, literal_idx: 5 }" # Load constant 2
- "Mul { dest: 0, left: 1, right: 3 }" # x * 2 (result in register 0)
- "RuleReturn {}" # Return result
want_result: 12
- note: function_call_undefined_result
description: Test function call that returns undefined due to failed condition
example_rego: "safe_div(1, 0) where safe_div(x, y) := x / y if y != 0"
literals:
- {}
- 0 # Rule index for safe_div function
- 1 # Numerator
- 0 # Denominator (zero)
instruction_params:
function_call_params:
- func: 0 # Register containing function rule index
dest: 3 # Destination register for result
args: [1, 2] # Argument registers
rule_infos:
- rule_type: Complete
definitions:
- [5] # Entry point for safe_div function body
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
- "Load { dest: 1, literal_idx: 2 }" # Load numerator
- "Load { dest: 2, literal_idx: 3 }" # Load denominator
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 3 }" # Return result
# Function body with condition that fails
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "LoadFalse { dest: 4 }" # Condition fails (simulated y == 0)
- "AssertCondition { condition: 4 }" # Assert fails, body fails
- "Div { dest: 0, left: 1, right: 2 }" # This won't execute
- "RuleReturn {}" # This won't execute
want_result: "#undefined"
- note: function_call_with_object_result
description: Test function call that returns an object
example_rego: "make_pair(1, 2) where make_pair(x, y) := {\"first\": x, \"second\": y}"
literals:
- {}
- 0 # Rule index for make_pair function
- 1 # First value
- 2 # Second value
- "first" # Key for first element
- "second" # Key for second element
instruction_params:
object_create_params:
- dest: 6
template_literal_idx: 0
literal_key_fields: []
fields: []
function_call_params:
- func: 0 # Register containing function rule index
dest: 5 # Destination register for result
args: [1, 2] # Argument registers
rule_infos:
- rule_type: Complete
definitions:
- [5] # Entry point for make_pair function body
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
- "Load { dest: 1, literal_idx: 2 }" # Load first value
- "Load { dest: 2, literal_idx: 3 }" # Load second value
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 5 }" # Return result
# Function body
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "ObjectCreate { params_index: 0 }"
- "Load { dest: 3, literal_idx: 4 }" # Load "first" key
- "ObjectSet { obj: 6, key: 3, value: 1 }" # Set first: x
- "Load { dest: 4, literal_idx: 5 }" # Load "second" key
- "ObjectSet { obj: 6, key: 4, value: 2 }" # Set second: y
- "Move { dest: 0, src: 6 }" # Move object to result register
- "RuleReturn {}" # Return object
want_result: {"first": 1, "second": 2}
- note: function_call_with_array_result
description: Test function call that returns an array
example_rego: "make_range(3, 5) where make_range(start, end) := [start, end]"
literals:
- {}
- 0 # Rule index for make_range function
- 3 # Start value
- 5 # End value
instruction_params:
function_call_params:
- func: 0 # Register containing function rule index
dest: 3 # Destination register for result
args: [1, 2] # Argument registers
rule_infos:
- rule_type: Complete
definitions:
- [5] # Entry point for make_range function body
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
- "Load { dest: 1, literal_idx: 2 }" # Load start value
- "Load { dest: 2, literal_idx: 3 }" # Load end value
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 3 }" # Return result
# Function body
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "ArrayNew { dest: 4 }" # Create new array
- "ArrayPush { arr: 4, value: 1 }" # Push start value
- "ArrayPush { arr: 4, value: 2 }" # Push end value
- "Move { dest: 0, src: 4 }" # Move array to result register
- "RuleReturn {}" # Return array
want_result: [3, 5]
- note: function_call_with_comparison
description: Test function that performs comparison
example_rego: "max(7, 3) where max(x, y) := x if x >= y"
literals:
- {}
- 0 # Rule index for max function
- 7 # First value
- 3 # Second value
instruction_params:
function_call_params:
- func: 0 # Register containing function rule index
dest: 3 # Destination register for result
args: [1, 2] # Argument registers
rule_infos:
- rule_type: Complete
definitions:
- [5] # Entry point for max function body
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
- "Load { dest: 1, literal_idx: 2 }" # Load first value (7)
- "Load { dest: 2, literal_idx: 3 }" # Load second value (3)
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 3 }" # Return result
# Function body: return x if x >= y
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "Ge { dest: 4, left: 1, right: 2 }" # x >= y
- "AssertCondition { condition: 4 }" # Assert condition
- "Move { dest: 0, src: 1 }" # Return x (move to result register)
- "RuleReturn {}" # Return result
want_result: 7
- note: function_call_three_args
description: Test function call with three arguments
example_rego: "sum_three(2, 3, 4) where sum_three(a, b, c) := a + b + c"
literals:
- {}
- 0 # Rule index for sum_three function
- 2 # First argument
- 3 # Second argument
- 4 # Third argument
instruction_params:
function_call_params:
- func: 0 # Register containing function rule index
dest: 4 # Destination register for result
args: [1, 2, 3] # Argument registers
rule_infos:
- rule_type: Complete
definitions:
- [6] # Entry point for sum_three function body
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
- "Load { dest: 1, literal_idx: 2 }" # Load first argument (2)
- "Load { dest: 2, literal_idx: 3 }" # Load second argument (3)
- "Load { dest: 3, literal_idx: 4 }" # Load third argument (4)
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 4 }" # Return result
# Function body: a + b + c
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "Add { dest: 5, left: 1, right: 2 }" # a + b
- "Add { dest: 0, left: 5, right: 3 }" # (a + b) + c (result in register 0)
- "RuleReturn {}" # Return result
want_result: 9
- note: function_call_inconsistent_definitions
description: Test function with multiple definitions that produce different values (should fail)
example_rego: "f(5) where f(x) := x + 1; f(x) := x + 2"
literals:
- {}
- 0 # Rule index for f function
- 5 # Argument value
- 1 # Constant 1
- 2 # Constant 2
instruction_params:
function_call_params:
- func: 0 # Register containing function rule index
dest: 2 # Destination register for result
args: [1] # Argument registers
rule_infos:
- rule_type: Complete
definitions:
- [4] # First definition: x + 1
- [8] # Second definition: x + 2 (should produce different result)
entry_points:
"data.test.compute": [4, 8] # Multiple definitions for the same function
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
- "Load { dest: 1, literal_idx: 2 }" # Load argument (5)
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 2 }" # Return result
# First definition: x + 1 (entry point 4)
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "Load { dest: 3, literal_idx: 3 }" # Load constant 1
- "Add { dest: 0, left: 1, right: 3 }" # x + 1 = 6 (result in register 0)
- "RuleReturn {}" # Return result
# Second definition: x + 2 (entry point 8)
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "Load { dest: 4, literal_idx: 4 }" # Load constant 2
- "Add { dest: 0, left: 1, right: 4 }" # x + 2 = 7 (result in register 0)
- "RuleReturn {}" # Return result
want_result: "#undefined"

View File

@@ -0,0 +1,14 @@
# Halt Instruction Test Suite
# Ensures Halt returns register 0 and ignores trailing instructions.
cases:
- note: halt_returns_register_zero
description: Halt ends execution and returns register 0
literals:
- 42
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Halt"
- "Load { dest: 1, literal_idx: 0 }"
- "Return { value: 1 }"
want_result: 42

View File

@@ -0,0 +1,137 @@
# HostAwait integration test suite
# Verifies host suspension across execution modes and optional run-to-completion fallbacks.
cases:
- note: host_await_single_response
description: Single HostAwait resumes with provided response in every execution mode
literals:
- "ping"
- "await-0"
instructions:
- "ArrayNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 3, literal_idx: 1 }"
- "HostAwait { dest: 2, arg: 1, id: 3 }"
- "ArrayPush { arr: 0, value: 2 }"
- "Return { value: 0 }"
host_await_responses:
- id: "await-0"
value: "pong"
want_result:
- "pong"
- note: host_await_multiple_responses
description: Multiple HostAwait instructions consume a queued sequence of responses
literals:
- 7
- "await-first"
- "await-second"
instructions:
- "ArrayNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 4, literal_idx: 1 }"
- "HostAwait { dest: 2, arg: 1, id: 4 }"
- "Load { dest: 5, literal_idx: 2 }"
- "HostAwait { dest: 3, arg: 2, id: 5 }"
- "ArrayPush { arr: 0, value: 2 }"
- "ArrayPush { arr: 0, value: 3 }"
- "Return { value: 0 }"
host_await_responses_run_to_completion:
- id: "await-first"
value: 42
- id: "await-second"
value: 43
host_await_responses_suspendable:
- id: "await-first"
value: 42
- id: "await-second"
value: 43
want_result:
- 42
- 43
- note: host_await_suspendable_only
description: Run-to-completion failure due to missing HostAwait response is ignored when flagged
literals:
- "payload"
- "await-single"
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "HostAwait { dest: 1, arg: 0, id: 2 }"
- "Return { value: 1 }"
host_await_responses_suspendable:
- id: "await-single"
value: "resume-value"
ignore_run_to_completion_hostawait_failure: true
want_result: "resume-value"
- note: host_await_multiple_sequential
description: Three sequential HostAwait calls with different responses
literals:
- "first"
- "second"
- "third"
- "id-1"
- "id-2"
- "id-3"
instructions:
- "ArrayNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 10, literal_idx: 3 }"
- "HostAwait { dest: 2, arg: 1, id: 10 }"
- "ArrayPush { arr: 0, value: 2 }"
- "Load { dest: 3, literal_idx: 1 }"
- "Load { dest: 11, literal_idx: 4 }"
- "HostAwait { dest: 4, arg: 3, id: 11 }"
- "ArrayPush { arr: 0, value: 4 }"
- "Load { dest: 5, literal_idx: 2 }"
- "Load { dest: 12, literal_idx: 5 }"
- "HostAwait { dest: 6, arg: 5, id: 12 }"
- "ArrayPush { arr: 0, value: 6 }"
- "Return { value: 0 }"
host_await_responses:
- id: "id-1"
value: "response-1"
- id: "id-2"
value: "response-2"
- id: "id-3"
value: "response-3"
want_result: ["response-1", "response-2", "response-3"]
- note: host_await_in_loop_body
description: HostAwait inside loop body - suspend/resume per iteration
literals:
- 1
- 2
- "id-1"
- "id-2"
instruction_params:
loop_params:
- mode: "ForEach"
collection: 5
key_reg: 10
value_reg: 11
result_reg: 12
body_start: 7
loop_end: 11
instructions:
- "ArrayNew { dest: 5 }" # Collection [1, 2]
- "Load { dest: 1, literal_idx: 0 }"
- "ArrayPush { arr: 5, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "ArrayPush { arr: 5, value: 2 }"
- "ArrayNew { dest: 0 }" # Result array
- "LoopStart { params_index: 0 }"
# HostAwait with dynamic ID based on loop value
- "Load { dest: 13, literal_idx: 2 }" # Load base ID
- "HostAwait { dest: 14, arg: 11, id: 13 }"
- "ArrayPush { arr: 0, value: 14 }"
- "LoopNext { body_start: 7, loop_end: 11 }"
- "Return { value: 0 }"
host_await_responses:
- id: "id-1"
value: "loop-response-1"
- id: "id-1"
value: "loop-response-2"
want_result: ["loop-response-1", "loop-response-2"]

View File

@@ -0,0 +1,28 @@
# Host Await Failures Test Suite
# Validates HostAwait error surfaces under different execution modes.
cases:
- note: host_await_missing_response
description: Run-to-completion mode errors when responses absent
literals:
- "request"
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "HostAwait { dest: 1, arg: 0, id: 0 }"
- "Return { value: 1 }"
want_error: "HostAwait executed but no response provided"
- note: host_await_ignore_flag
description: ignore_run_to_completion_hostawait_failure guards missing responses
literals:
- "request"
- "response"
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "HostAwait { dest: 1, arg: 0, id: 0 }"
- "Return { value: 1 }"
host_await_responses_suspendable:
- id: "request"
value: "response"
ignore_run_to_completion_hostawait_failure: true
want_result: "response"

View File

@@ -0,0 +1,241 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Indexed Access Test Suite
# Exercises literal indexing, chained indexing, and error paths.
cases:
- note: index_literal_success
description: IndexLiteral retrieves value when literal key exists
literals:
- { key: true }
- "key"
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
instructions:
- "ObjectCreate { params_index: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "LoadTrue { dest: 2 }"
- "ObjectSet { obj: 0, key: 1, value: 2 }"
- "IndexLiteral { dest: 3, container: 0, literal_idx: 1 }"
- "Return { value: 3 }"
want_result: true
- note: index_literal_bad_literal
description: IndexLiteral with invalid literal index triggers error
literals:
- {}
instructions:
- "ObjectCreate { params_index: 0 }"
- "IndexLiteral { dest: 1, container: 0, literal_idx: 99 }"
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
want_error: "Literal index 99 out of bounds"
- note: chained_index_mixed_path
description: ChainedIndex handles literal and register path components
literals:
- {}
- "outer"
- "inner"
- "value"
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
chained_index_params:
- dest: 5
root: 0
path_components:
- literal_idx: 1
- register: 3
instructions:
- "ObjectCreate { params_index: 0 }"
- "Load { dest: 3, literal_idx: 2 }" # inner
- "Load { dest: 4, literal_idx: 3 }" # value
- "Load { dest: 2, literal_idx: 0 }" # nested object builder
- "ObjectSet { obj: 2, key: 3, value: 4 }"
- "Load { dest: 1, literal_idx: 1 }" # outer
- "ObjectSet { obj: 0, key: 1, value: 2 }"
- "ChainedIndex { params_index: 0 }"
- "Return { value: 5 }"
want_result: "value"
- note: chained_index_undefined_propagation
description: ChainedIndex yields undefined when intermediate missing
literals:
- {}
- "missing"
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
chained_index_params:
- dest: 2
root: 0
path_components:
- literal_idx: 1
- literal_idx: 1
instructions:
- "ObjectCreate { params_index: 0 }"
- "ChainedIndex { params_index: 0 }"
- "Return { value: 2 }"
want_result: "#undefined"
- note: chained_index_5_components
description: ChainedIndex with 5 path components
example_rego: "data.a.b.c.d.e"
literals:
- {}
- "a"
- "b"
- "c"
- "d"
- "e"
- "final_value"
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
chained_index_params:
- dest: 10
root: 0
path_components:
- literal_idx: 1 # a
- literal_idx: 2 # b
- literal_idx: 3 # c
- literal_idx: 4 # d
- literal_idx: 5 # e
instructions:
- "ObjectCreate { params_index: 0 }" # r0 = {}
# Build nested structure: {a: {b: {c: {d: {e: "final_value"}}}}}
- "Load { dest: 1, literal_idx: 0 }" # innermost object
- "Load { dest: 2, literal_idx: 5 }" # key "e"
- "Load { dest: 3, literal_idx: 6 }" # value "final_value"
- "ObjectSet { obj: 1, key: 2, value: 3 }" # {e: "final_value"}
- "Load { dest: 4, literal_idx: 0 }" # d level object
- "Load { dest: 5, literal_idx: 4 }" # key "d"
- "ObjectSet { obj: 4, key: 5, value: 1 }" # {d: {e: ...}}
- "Load { dest: 6, literal_idx: 0 }" # c level object
- "Load { dest: 7, literal_idx: 3 }" # key "c"
- "ObjectSet { obj: 6, key: 7, value: 4 }" # {c: {d: ...}}
- "Load { dest: 8, literal_idx: 0 }" # b level object
- "Load { dest: 9, literal_idx: 2 }" # key "b"
- "ObjectSet { obj: 8, key: 9, value: 6 }" # {b: {c: ...}}
- "Load { dest: 11, literal_idx: 1 }" # key "a"
- "ObjectSet { obj: 0, key: 11, value: 8 }" # {a: {b: ...}}
- "ChainedIndex { params_index: 0 }"
- "Return { value: 10 }"
want_result: "final_value"
- note: chained_index_all_registers
description: ChainedIndex with all register components (no literals)
example_rego: "obj[key1][key2]"
literals:
- {}
- "level1"
- "level2"
- "result"
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
chained_index_params:
- dest: 10
root: 0
path_components:
- register: 5 # key1
- register: 6 # key2
instructions:
- "ObjectCreate { params_index: 0 }" # r0 = {}
# Build structure: {level1: {level2: "result"}}
- "Load { dest: 1, literal_idx: 0 }" # inner object
- "Load { dest: 2, literal_idx: 2 }" # "level2"
- "Load { dest: 3, literal_idx: 3 }" # "result"
- "ObjectSet { obj: 1, key: 2, value: 3 }" # {level2: "result"}
- "Load { dest: 4, literal_idx: 1 }" # "level1"
- "ObjectSet { obj: 0, key: 4, value: 1 }" # {level1: {...}}
# Set up dynamic keys in registers
- "Load { dest: 5, literal_idx: 1 }" # key1 = "level1"
- "Load { dest: 6, literal_idx: 2 }" # key2 = "level2"
- "ChainedIndex { params_index: 0 }"
- "Return { value: 10 }"
want_result: "result"
- note: index_with_null_key
description: Index with null key returns undefined
example_rego: "obj[null]"
literals:
- {"key": "value"}
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "LoadNull { dest: 1 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "Return { value: 2 }"
want_result: "#undefined"
- note: index_with_undefined_key
description: Index with undefined key returns undefined
example_rego: "obj[undefined_var]"
literals:
- {"key": "value"}
instructions:
- "Load { dest: 0, literal_idx: 0 }"
# r1 is undefined (never loaded)
- "Index { dest: 2, container: 0, key: 1 }"
- "Return { value: 2 }"
want_result: "#undefined"
- note: index_with_boolean_key
description: Index with boolean key
example_rego: "obj[true]"
literals:
- {true: "bool_value", "regular": "string_value"}
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "LoadTrue { dest: 1 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "Return { value: 2 }"
want_result: "bool_value"
- note: index_with_empty_string_key
description: Index with empty string key
example_rego: "obj[\"\"]"
literals:
- {"": "empty_key_value", "other": "other_value"}
- ""
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "Return { value: 2 }"
want_result: "empty_key_value"
- note: index_with_number_key_on_object
description: Index object with number key
example_rego: "obj[42]"
literals:
- {42: "number_value", "str": "string_value"}
- 42
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "Return { value: 2 }"
want_result: "number_value"

View File

@@ -0,0 +1,432 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Integration Scenarios Test Suite
# Tests real-world Rego policy patterns
# Covers RBAC, filtering, transforms, and complex policy workflows
cases:
- note: rbac_admin_check
description: Role-based access control - check admin role
example_rego: "allow { user.roles[_] == \"admin\" }"
literals:
- {"roles": ["user", "admin", "developer"]}
- "roles"
- "admin"
instruction_params:
loop_params:
- mode: "Any"
collection: 2
key_reg: 3
value_reg: 4
result_reg: 6
body_start: 5
loop_end: 8
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "Load { dest: 5, literal_idx: 2 }"
- "LoopStart { params_index: 0 }"
- "Eq { dest: 7, left: 4, right: 5 }"
- "AssertCondition { condition: 7 }"
- "LoopNext { body_start: 5, loop_end: 8 }"
- "Return { value: 6 }"
want_result: true
- note: rbac_no_matching_role
description: RBAC check fails when role not present
example_rego: "allow { user.roles[_] == \"superadmin\" }"
literals:
- {"roles": ["user", "developer"]}
- "roles"
- "superadmin"
instruction_params:
loop_params:
- mode: "Any"
collection: 2
key_reg: 3
value_reg: 4
result_reg: 6
body_start: 5
loop_end: 8
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "Load { dest: 5, literal_idx: 2 }"
- "LoopStart { params_index: 0 }"
- "Eq { dest: 7, left: 4, right: 5 }"
- "AssertCondition { condition: 7 }"
- "LoopNext { body_start: 5, loop_end: 8 }"
- "Return { value: 6 }"
want_result: false
- note: filtering_by_owner
description: Filter resources by owner ID
example_rego: "[x | x = resources[_]; x.owner == user.id]"
literals:
- [{"name": "res1", "owner": "user123"}, {"name": "res2", "owner": "user456"}, {"name": "res3", "owner": "user123"}]
- {"id": "user123"}
- "owner"
- "id"
instruction_params:
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 1
value_reg: 2
result_reg: 11
body_start: 4
loop_end: 12
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "ArrayNew { dest: 8 }"
- "Load { dest: 9, literal_idx: 1 }"
- "LoopStart { params_index: 0 }"
- "Load { dest: 3, literal_idx: 2 }"
- "Index { dest: 4, container: 2, key: 3 }"
- "Load { dest: 5, literal_idx: 3 }"
- "Index { dest: 6, container: 9, key: 5 }"
- "Eq { dest: 7, left: 4, right: 6 }"
- "AssertCondition { condition: 7 }"
- "ArrayPush { arr: 8, value: 2 }"
- "LoopNext { body_start: 4, loop_end: 12 }"
- "Return { value: 8 }"
want_result: [{"name": "res1", "owner": "user123"}, {"name": "res3", "owner": "user123"}]
- note: filtering_empty_result
description: Filter returns empty array when no matches
example_rego: "[x | x = resources[_]; x.owner == \"nonexistent\"]"
literals:
- [{"name": "res1", "owner": "user123"}, {"name": "res2", "owner": "user456"}]
- "owner"
- "nonexistent"
instruction_params:
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 1
value_reg: 2
result_reg: 8
body_start: 4
loop_end: 10
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "ArrayNew { dest: 7 }"
- "Load { dest: 5, literal_idx: 2 }"
- "LoopStart { params_index: 0 }"
- "Load { dest: 3, literal_idx: 1 }"
- "Index { dest: 4, container: 2, key: 3 }"
- "Eq { dest: 6, left: 4, right: 5 }"
- "AssertCondition { condition: 6 }"
- "ArrayPush { arr: 7, value: 2 }"
- "LoopNext { body_start: 4, loop_end: 10 }"
- "Return { value: 7 }"
want_result: []
- note: transform_multiply_values
description: Transform values by multiplying above threshold
example_rego: "{k: v * 2 | v = data.metrics[k]; v > threshold}"
literals:
- {"cpu": 50, "memory": 80, "disk": 30}
- 40
- 2
- {}
instruction_params:
object_create_params:
- dest: 4
template_literal_idx: 3
literal_key_fields: []
fields: []
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 1
value_reg: 2
result_reg: 9
body_start: 4
loop_end: 10
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 3, literal_idx: 1 }"
- "ObjectCreate { params_index: 0 }"
- "LoopStart { params_index: 0 }"
- "Gt { dest: 5, left: 2, right: 3 }"
- "AssertCondition { condition: 5 }"
- "Load { dest: 7, literal_idx: 2 }"
- "Mul { dest: 8, left: 2, right: 7 }"
- "ObjectSet { obj: 4, key: 1, value: 8 }"
- "LoopNext { body_start: 4, loop_end: 10 }"
- "Return { value: 4 }"
want_result: {"cpu": 100, "memory": 160}
- note: transform_all_values
description: Transform all values in map
example_rego: "{k: v + 10 | v = data[k]}"
literals:
- {"a": 1, "b": 2, "c": 3}
- 10
- {}
instruction_params:
object_create_params:
- dest: 4
template_literal_idx: 2
literal_key_fields: []
fields: []
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 1
value_reg: 2
result_reg: 6
body_start: 4
loop_end: 7
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 3, literal_idx: 1 }"
- "ObjectCreate { params_index: 0 }"
- "LoopStart { params_index: 0 }"
- "Add { dest: 5, left: 2, right: 3 }"
- "ObjectSet { obj: 4, key: 1, value: 5 }"
- "LoopNext { body_start: 4, loop_end: 7 }"
- "Return { value: 4 }"
want_result: {"a": 11, "b": 12, "c": 13}
- note: nested_filtering_and_transform
description: Filter then transform (chained operations)
example_rego: "[x * 2 | x = numbers[_]; x > 5]"
literals:
- [3, 7, 4, 9, 2, 8]
- 5
- 2
instruction_params:
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 1
value_reg: 2
result_reg: 6
body_start: 5
loop_end: 10
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "ArrayNew { dest: 5 }"
- "Load { dest: 9, literal_idx: 1 }"
- "Load { dest: 10, literal_idx: 2 }"
- "LoopStart { params_index: 0 }"
- "Gt { dest: 3, left: 2, right: 9 }"
- "AssertCondition { condition: 3 }"
- "Mul { dest: 4, left: 2, right: 10 }"
- "ArrayPush { arr: 5, value: 4 }"
- "LoopNext { body_start: 5, loop_end: 10 }"
- "Return { value: 5 }"
want_result: [14, 18, 16]
- note: multi_field_validation
description: Validate multiple fields meet criteria
example_rego: "valid { user.age >= 18; user.verified == true; user.status == \"active\" }"
literals:
- {"age": 25, "verified": true, "status": "active"}
- "age"
- 18
- "verified"
- "status"
- "active"
instructions:
- "Load { dest: 0, literal_idx: 0 }"
# Check age >= 18
- "Load { dest: 1, literal_idx: 1 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "Load { dest: 3, literal_idx: 2 }"
- "Ge { dest: 4, left: 2, right: 3 }"
- "Load { dest: 5, literal_idx: 3 }"
- "Index { dest: 6, container: 0, key: 5 }"
- "LoadTrue { dest: 7 }"
- "Eq { dest: 8, left: 6, right: 7 }"
- "And { dest: 9, left: 4, right: 8 }"
- "Load { dest: 10, literal_idx: 4 }"
- "Index { dest: 11, container: 0, key: 10 }"
- "Load { dest: 12, literal_idx: 5 }"
- "Eq { dest: 13, left: 11, right: 12 }"
- "And { dest: 14, left: 9, right: 13 }"
- "Return { value: 14 }"
want_result: true
- note: multi_field_validation_failure
description: Multi-field validation fails when one criterion not met
example_rego: "valid { user.age >= 18; user.verified == true; user.status == \"active\" }"
literals:
- {"age": 25, "verified": false, "status": "active"}
- "age"
- 18
- "verified"
- "status"
- "active"
instructions:
- "Load { dest: 0, literal_idx: 0 }"
# Check age >= 18
- "Load { dest: 1, literal_idx: 1 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "Load { dest: 3, literal_idx: 2 }"
- "Ge { dest: 4, left: 2, right: 3 }"
- "Load { dest: 5, literal_idx: 3 }"
- "Index { dest: 6, container: 0, key: 5 }"
- "LoadTrue { dest: 7 }"
- "Eq { dest: 8, left: 6, right: 7 }"
- "And { dest: 9, left: 4, right: 8 }"
- "Load { dest: 10, literal_idx: 4 }"
- "Index { dest: 11, container: 0, key: 10 }"
- "Load { dest: 12, literal_idx: 5 }"
- "Eq { dest: 13, left: 11, right: 12 }"
- "And { dest: 14, left: 9, right: 13 }"
- "Return { value: 14 }"
want_result: false
- note: set_membership_check
description: Check if value is in allowed set
example_rego: "allowed { input.action in {\"read\", \"write\", \"delete\"} }"
literals:
- {"action": "read"}
- "action"
- "read"
- "write"
- "delete"
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Index { dest: 2, container: 0, key: 1 }"
# Create allowed set
- "SetNew { dest: 3 }"
- "Load { dest: 4, literal_idx: 2 }"
- "SetAdd { set: 3, value: 4 }"
- "Load { dest: 5, literal_idx: 3 }"
- "SetAdd { set: 3, value: 5 }"
- "Load { dest: 6, literal_idx: 4 }"
- "SetAdd { set: 3, value: 6 }"
# Check membership
- "Contains { dest: 7, collection: 3, value: 2 }"
- "Return { value: 7 }"
want_result: true
- note: set_membership_denied
description: Check fails when value not in allowed set
example_rego: "allowed { input.action in {\"read\", \"write\", \"delete\"} }"
literals:
- {"action": "execute"}
- "action"
- "read"
- "write"
- "delete"
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Index { dest: 2, container: 0, key: 1 }"
# Create allowed set
- "SetNew { dest: 3 }"
- "Load { dest: 4, literal_idx: 2 }"
- "SetAdd { set: 3, value: 4 }"
- "Load { dest: 5, literal_idx: 3 }"
- "SetAdd { set: 3, value: 5 }"
- "Load { dest: 6, literal_idx: 4 }"
- "SetAdd { set: 3, value: 6 }"
# Check membership
- "Contains { dest: 7, collection: 3, value: 2 }"
- "Return { value: 7 }"
want_result: false
- note: aggregation_count
description: Count items matching condition
example_rego: "count([x | x = items[_]; x.status == \"active\"])"
literals:
- [{"id": 1, "status": "active"}, {"id": 2, "status": "inactive"}, {"id": 3, "status": "active"}]
- "status"
- "active"
instruction_params:
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 1
value_reg: 2
result_reg: 8
body_start: 4
loop_end: 10
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "ArrayNew { dest: 6 }"
- "Load { dest: 9, literal_idx: 2 }"
- "LoopStart { params_index: 0 }"
- "Load { dest: 3, literal_idx: 1 }"
- "Index { dest: 4, container: 2, key: 3 }"
- "Eq { dest: 5, left: 4, right: 9 }"
- "AssertCondition { condition: 5 }"
- "ArrayPush { arr: 6, value: 2 }"
- "LoopNext { body_start: 4, loop_end: 10 }"
- "Return { value: 6 }"
want_result: [{"id": 1, "status": "active"}, {"id": 3, "status": "active"}]
- note: hierarchical_permission_check
description: Check nested permission structure
example_rego: "allow { data.permissions[input.user][input.resource] == \"allow\" }"
literals:
- {"permissions": {"alice": {"doc1": "allow", "doc2": "deny"}, "bob": {"doc1": "deny"}}}
- {"user": "alice", "resource": "doc1"}
- "permissions"
- "user"
- "resource"
- "allow"
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
# Load permissions
- "Load { dest: 2, literal_idx: 2 }"
- "Index { dest: 3, container: 0, key: 2 }"
# Load user
- "Load { dest: 4, literal_idx: 3 }"
- "Index { dest: 5, container: 1, key: 4 }"
# Index permissions by user
- "Index { dest: 6, container: 3, key: 5 }"
# Load resource
- "Load { dest: 7, literal_idx: 4 }"
- "Index { dest: 8, container: 1, key: 7 }"
# Index user permissions by resource
- "Index { dest: 9, container: 6, key: 8 }"
# Check if "allow"
- "Load { dest: 10, literal_idx: 5 }"
- "Eq { dest: 11, left: 9, right: 10 }"
- "Return { value: 11 }"
want_result: true
- note: default_value_pattern
description: Provide default value when path undefined
example_rego: "timeout := data.config.timeout; timeout == 30 if not data.config.timeout"
literals:
- {}
- "config"
- "timeout"
- 30
instruction_params:
loop_params:
- mode: "Any"
collection: 7
key_reg: 8
value_reg: 9
result_reg: 10
body_start: 10
loop_end: 13
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "Load { dest: 3, literal_idx: 2 }"
- "Index { dest: 4, container: 2, key: 3 }"
- "Load { dest: 5, literal_idx: 3 }"
- "Contains { dest: 6, collection: 2, value: 3 }"
- "ArrayNew { dest: 7 }"
- "ArrayPush { arr: 7, value: 6 }"
- "LoopStart { params_index: 0 }"
- "AssertCondition { condition: 9 }"
- "Move { dest: 5, src: 4 }"
- "LoopNext { body_start: 10, loop_end: 13 }"
- "Return { value: 5 }"
want_result: 30

View File

@@ -0,0 +1,351 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Interpreter Operator Compatibility Test Suite
# Tests that VM operators behave exactly like interpreter operators
# Focuses on edge cases like undefined value handling, division by zero, etc.
cases:
# Undefined Value Arithmetic Tests
- note: undefined_add
description: Test addition with undefined value
example_rego: "input.nonexistent + 5"
input: {} # Empty input to create undefined access
literals:
- "nonexistent"
- 5
instructions:
- "LoadInput { dest: 0 }" # Load input into register 0
- "Load { dest: 1, literal_idx: 0 }" # Load string "nonexistent" for indexing
- "Load { dest: 2, literal_idx: 1 }" # Load 5 into register 2
- "Index { dest: 3, container: 0, key: 1 }" # Access input.nonexistent (undefined)
- "Add { dest: 4, left: 3, right: 2 }" # undefined + 5
- "Return { value: 4 }"
want_result: "#undefined"
- note: undefined_sub
description: Test subtraction with undefined value
example_rego: "input.nonexistent - 5"
input: {}
literals:
- "nonexistent"
- 5
instructions:
- "LoadInput { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Index { dest: 3, container: 0, key: 1 }"
- "Sub { dest: 4, left: 3, right: 2 }"
- "Return { value: 4 }"
want_result: "#undefined"
- note: undefined_mul
description: Test multiplication with undefined value
example_rego: "input.nonexistent * 5"
input: {}
literals:
- "nonexistent"
- 5
instructions:
- "LoadInput { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Index { dest: 3, container: 0, key: 1 }"
- "Mul { dest: 4, left: 3, right: 2 }"
- "Return { value: 4 }"
want_result: "#undefined"
# Division by Zero Tests
- note: division_by_zero
description: Test division by zero returns undefined (non-strict mode)
example_rego: "5 / 0"
literals:
- 5
- 0
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Div { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: "#undefined"
- note: modulo_by_zero
description: Test modulo by zero returns undefined (non-strict mode)
example_rego: "5 % 0"
literals:
- 5
- 0
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Mod { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: "#undefined"
# Modulo Float Tests
- note: modulo_float
description: Test modulo with floating point numbers (should error)
example_rego: "5.5 % 2"
literals:
- 5.5
- 2
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Mod { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_error: "modulo on floating-point number"
# Undefined Comparison Tests
- note: undefined_eq
description: Test equality comparison with undefined value
example_rego: "input.nonexistent == 5"
input: {}
literals:
- "nonexistent"
- 5
instructions:
- "LoadInput { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Index { dest: 3, container: 0, key: 1 }"
- "Eq { dest: 4, left: 3, right: 2 }"
- "Return { value: 4 }"
want_result: "#undefined"
- note: undefined_ne
description: Test inequality comparison with undefined value
example_rego: "input.nonexistent != 5"
input: {}
literals:
- "nonexistent"
- 5
instructions:
- "LoadInput { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Index { dest: 3, container: 0, key: 1 }"
- "Ne { dest: 4, left: 3, right: 2 }"
- "Return { value: 4 }"
want_result: "#undefined"
- note: undefined_lt
description: Test less than comparison with undefined value
example_rego: "input.nonexistent < 5"
input: {}
literals:
- "nonexistent"
- 5
instructions:
- "LoadInput { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Index { dest: 3, container: 0, key: 1 }"
- "Lt { dest: 4, left: 3, right: 2 }"
- "Return { value: 4 }"
want_result: "#undefined"
- note: undefined_le
description: Test less than or equal comparison with undefined value
example_rego: "input.nonexistent <= 5"
input: {}
literals:
- "nonexistent"
- 5
instructions:
- "LoadInput { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Index { dest: 3, container: 0, key: 1 }"
- "Le { dest: 4, left: 3, right: 2 }"
- "Return { value: 4 }"
want_result: "#undefined"
- note: undefined_gt
description: Test greater than comparison with undefined value
example_rego: "input.nonexistent > 5"
input: {}
literals:
- "nonexistent"
- 5
instructions:
- "LoadInput { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Index { dest: 3, container: 0, key: 1 }"
- "Gt { dest: 4, left: 3, right: 2 }"
- "Return { value: 4 }"
want_result: "#undefined"
- note: undefined_ge
description: Test greater than or equal comparison with undefined value
example_rego: "input.nonexistent >= 5"
input: {}
literals:
- "nonexistent"
- 5
instructions:
- "LoadInput { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Index { dest: 3, container: 0, key: 1 }"
- "Ge { dest: 4, left: 3, right: 2 }"
- "Return { value: 4 }"
want_result: "#undefined"
# Number Type Precision Tests
- note: number_add_precision
description: Test Number type addition preserves precision
example_rego: "1.1 + 2.2"
literals:
- 1.1
- 2.2
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Add { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: 3.3
- note: number_sub_precision
description: Test Number type subtraction preserves precision
example_rego: "5.5 - 2.2"
literals:
- 5.5
- 2.2
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Sub { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: 3.3
- note: number_mul_precision
description: Test Number type multiplication preserves precision
example_rego: "2.5 * 4.0"
literals:
- 2.5
- 4.0
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Mul { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: 10.0
- note: number_div_precision
description: Test Number type division preserves precision
example_rego: "7.5 / 2.5"
literals:
- 7.5
- 2.5
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Div { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: 3.0
# Integer Modulo Tests
- note: integer_modulo
description: Test integer modulo works correctly
example_rego: "7 % 3"
literals:
- 7
- 3
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Mod { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: 1
# Value Ordering Tests
- note: value_ordering_null_bool
description: Test null < bool ordering
example_rego: "null < true"
literals:
- null
- true
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Lt { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: true
- note: value_ordering_bool_number
description: Test bool < number ordering
example_rego: "true < 1"
literals:
- true
- 1
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Lt { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: true
- note: value_ordering_number_string
description: Test number < string ordering
example_rego: "1 < \"a\""
literals:
- 1
- "a"
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Lt { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: true
# Edge Cases for Mixed Undefined Operations
- note: undefined_both_operands
description: Test operation with both operands undefined
example_rego: "input.nonexistent1 + input.nonexistent2"
input: {}
literals:
- "nonexistent1"
- "nonexistent2"
instructions:
- "LoadInput { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Index { dest: 3, container: 0, key: 1 }"
- "Index { dest: 4, container: 0, key: 2 }"
- "Add { dest: 5, left: 3, right: 4 }"
- "Return { value: 5 }"
want_result: "#undefined"
- note: undefined_right_operand_arithmetic
description: Test arithmetic with right operand undefined
example_rego: "5 + input.nonexistent"
input: {}
literals:
- 5
- "nonexistent"
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "LoadInput { dest: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Index { dest: 3, container: 1, key: 2 }"
- "Add { dest: 4, left: 0, right: 3 }"
- "Return { value: 4 }"
want_result: "#undefined"
- note: undefined_right_operand_comparison
description: Test comparison with right operand undefined
example_rego: "5 == input.nonexistent"
input: {}
literals:
- 5
- "nonexistent"
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "LoadInput { dest: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Index { dest: 3, container: 1, key: 2 }"
- "Eq { dest: 4, left: 0, right: 3 }"
- "Return { value: 4 }"
want_result: "#undefined"

View File

@@ -0,0 +1,61 @@
# Invalid Collection Operations Test Suite
# Exercises error conditions for collection-specific instructions.
cases:
- note: object_set_on_non_object
description: ObjectSet on non-object register triggers error
literals:
- 1
- "key"
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "ObjectSet { obj: 0, key: 1, value: 1 }"
want_error: "Register 0 does not contain an object"
- note: array_push_on_non_array
description: ArrayPush on scalar register fails
literals:
- 1
- 2
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "ArrayPush { arr: 0, value: 1 }"
want_error: "Register 0 does not contain an array"
- note: set_add_on_non_set
description: SetAdd on scalar register fails
literals:
- 1
- 2
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "SetAdd { set: 0, value: 1 }"
want_error: "Register 0 does not contain a set"
- note: object_create_invalid_template
description: ObjectCreate fails when template literal is not object
literals:
- []
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
instructions:
- "ObjectCreate { params_index: 0 }"
want_error: "ObjectCreate: template is not an object"
- note: contains_on_scalar_returns_false
description: Contains on scalar should return false
literals:
- 1
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Contains { dest: 2, collection: 0, value: 1 }"
- "Return { value: 2 }"
want_result: false

View File

@@ -0,0 +1,250 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Load Data and Input Test Suite
# Tests LoadData and LoadInput instructions with various edge cases
# Covers empty, nested, undefined values, and interaction with virtual data lookups
cases:
- note: load_data_empty
description: LoadData from empty data context
example_rego: "data"
data: {}
literals: []
instructions:
- "LoadData { dest: 0 }"
- "Return { value: 0 }"
want_result: {}
- note: load_input_empty
description: LoadInput from empty input context
example_rego: "input"
input: {}
literals: []
instructions:
- "LoadInput { dest: 0 }"
- "Return { value: 0 }"
want_result: {}
- note: load_data_with_values
description: LoadData returns populated data object
example_rego: "data"
data: {}
literals: []
# This would require the test harness to support setting data
# For now, testing structure only
instructions:
- "LoadData { dest: 0 }"
- "Return { value: 0 }"
want_result: {} # Default empty since harness may not set data
- note: load_input_with_values
description: LoadInput returns populated input object
example_rego: "input"
input: {}
literals: []
instructions:
- "LoadInput { dest: 0 }"
- "Return { value: 0 }"
want_result: {} # Default empty
- note: load_data_and_index
description: LoadData followed by indexing
example_rego: "data.users"
data: {}
literals:
- "users"
instructions:
- "LoadData { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "Return { value: 2 }"
want_result: "#undefined" # users doesn't exist in empty data
- note: load_input_and_index
description: LoadInput followed by indexing
example_rego: "input.user.name"
input: {}
literals:
- "user"
- "name"
instructions:
- "LoadInput { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "Load { dest: 3, literal_idx: 1 }"
- "Index { dest: 4, container: 2, key: 3 }"
- "Return { value: 4 }"
want_result: "#undefined" # user doesn't exist in empty input
- note: load_data_multiple_times
description: LoadData can be called multiple times
example_rego: "data == data"
data: {}
literals: []
instructions:
- "LoadData { dest: 0 }"
- "LoadData { dest: 1 }"
- "Eq { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: true
- note: load_input_multiple_times
description: LoadInput can be called multiple times
example_rego: "input == input"
input: {}
literals: []
instructions:
- "LoadInput { dest: 0 }"
- "LoadInput { dest: 1 }"
- "Eq { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: true
- note: load_data_not_equal_to_input
description: data and input are separate contexts
example_rego: "data == input"
data: {}
input: {}
literals: []
instructions:
- "LoadData { dest: 0 }"
- "LoadInput { dest: 1 }"
- "Eq { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: true # Both empty {}
- note: load_data_in_loop
description: LoadData inside loop body
example_rego: "[ data | _ = [1, 2][_] ]"
data: {}
literals:
- 1
- 2
instruction_params:
array_create_params:
- dest: 0
elements: [1, 2]
loop_params:
- mode: ForEach
collection: 0
key_reg: 3
value_reg: 4
result_reg: 7
body_start: 5
loop_end: 8
instructions:
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "ArrayCreate { params_index: 0 }"
- "ArrayNew { dest: 6 }"
- "LoopStart { params_index: 0 }"
# Loop body starts
- "LoadData { dest: 5 }"
- "ArrayPush { arr: 6, value: 5 }"
- "LoopNext { body_start: 5, loop_end: 8 }"
# Loop done
- "Return { value: 6 }"
want_result: [{}, {}]
- note: load_input_in_loop
description: LoadInput inside loop body
example_rego: "[ input | _ = [1, 2][_] ]"
input: {}
literals:
- 1
- 2
instruction_params:
array_create_params:
- dest: 0
elements: [1, 2]
loop_params:
- mode: ForEach
collection: 0
key_reg: 3
value_reg: 4
result_reg: 7
body_start: 5
loop_end: 8
instructions:
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "ArrayCreate { params_index: 0 }"
- "ArrayNew { dest: 6 }"
- "LoopStart { params_index: 0 }"
# Loop body starts
- "LoadInput { dest: 5 }"
- "ArrayPush { arr: 6, value: 5 }"
- "LoopNext { body_start: 5, loop_end: 8 }"
# Loop done
- "Return { value: 6 }"
want_result: [{}, {}]
- note: load_data_in_arithmetic
description: Using LoadData result in arithmetic should fail
example_rego: "data + 1"
data: {}
literals:
- 1
instructions:
- "LoadData { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Add { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_error: "Cannot add Object({}) and Number(1)"
- note: load_input_in_comparison
description: Compare input to literal object
example_rego: "input == {}"
input: {}
literals:
- {}
instructions:
- "LoadInput { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Eq { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: true
- note: load_data_conditional
description: LoadData in conditional check
example_rego: "data.flag"
data: {}
literals:
- "flag"
instructions:
- "LoadData { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "LoadFalse { dest: 3 }"
- "Or { dest: 4, left: 2, right: 3 }"
- "Return { value: 4 }"
want_result: "#undefined" # flag is undefined
- note: load_data_chained_index
description: Deep path indexing on data
example_rego: "data.a.b.c.d"
data: {}
literals:
- "a"
- "b"
- "c"
- "d"
instruction_params:
chained_index_params:
- dest: 5
root: 0
path_components:
- register: 1
- register: 2
- register: 3
- register: 4
instructions:
- "LoadData { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Load { dest: 3, literal_idx: 2 }"
- "Load { dest: 4, literal_idx: 3 }"
- "ChainedIndex { params_index: 0 }"
- "Return { value: 5 }"
want_result: "#undefined"

View File

@@ -0,0 +1,43 @@
# Loop Invalid Iteration Test Suite
# Asserts loop error paths and instruction limit behavior.
cases:
- note: loop_start_on_scalar
description: LoopStart over scalar treats collection as empty and leaves result false
literals:
- 1
instruction_params:
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 1
value_reg: 2
result_reg: 3
body_start: 2
loop_end: 2
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "LoopStart { params_index: 0 }"
- "Return { value: 3 }"
want_result: false
- note: instruction_limit_exceeded
description: VM stops once max instruction limit reached
literals:
- [1, 2]
instruction_params:
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 1
value_reg: 2
result_reg: 3
body_start: 2
loop_end: 4
max_instructions: 3
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "LoopStart { params_index: 0 }"
- "LoopNext { body_start: 2, loop_end: 4 }"
- "Return { value: 3 }"
want_error: "exceeded maximum instruction limit"

View File

@@ -0,0 +1,160 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Array Comprehension Test Suite
# Tests array comprehensions - collect transformed values based on conditions
# Corresponds to Rego's "[transform | condition]" patterns
cases:
- note: array_simple_transform
description: Simple array comprehension with transformation
example_rego: |
# Transform array elements by doubling them
[x * 2 | x := [1, 2, 3][_]] # [2, 4, 6]
literals:
- 1
- 2
- 3
- 2 # multiplier
instruction_params:
comprehension_begin_params:
- mode: "Array"
collection_reg: 7
key_reg: 4
value_reg: 5
body_start: 8
comprehension_end: 11
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 8
body_start: 9
loop_end: 12
instructions:
- "ArrayNew { dest: 0 }" # Create input array [1, 2, 3] in register 0
- "Load { dest: 1, literal_idx: 0 }" # Load 1 into register 1
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
- "Load { dest: 2, literal_idx: 1 }" # Load 2 into register 2
- "ArrayPush { arr: 0, value: 2 }" # Push 2 to array
- "Load { dest: 3, literal_idx: 2 }" # Load 3 into register 3
- "ArrayPush { arr: 0, value: 3 }" # Push 3 to array
- "ComprehensionBegin { params_index: 0 }" # Start array comprehension and initialize result in register 7
- "LoopStart { params_index: 0 }" # Start loop over array elements
- "Load { dest: 6, literal_idx: 3 }" # Load multiplier 2 into register 6
- "Mul { dest: 9, left: 5, right: 6 }" # Multiply current value by 2, store result in register 9
- "ComprehensionYield { value_reg: 9 }" # Add transformed value to comprehension
- "LoopNext { body_start: 9, loop_end: 12 }" # Continue to next iteration or exit
- "Return { value: 7 }" # Return comprehension collection
want_result: [2, 4, 6]
- note: array_empty_input
description: Array comprehension with empty input
example_rego: |
# Transform empty array
[x + 5 | x := [][_]] # [] (empty array)
literals:
- 5 # addend
instruction_params:
comprehension_begin_params:
- mode: "Array"
collection_reg: 7
key_reg: 4
value_reg: 5
body_start: 2
comprehension_end: 6
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 8
body_start: 3
loop_end: 6
instructions:
- "ArrayNew { dest: 0 }" # Create empty input array in register 0
- "ComprehensionBegin { params_index: 0 }" # Start array comprehension and initialize result in register 7
- "LoopStart { params_index: 0 }" # Start loop over array elements
- "Load { dest: 6, literal_idx: 0 }" # Load addend 5 into register 6
- "Add { dest: 8, left: 5, right: 6 }" # Add 5 to current value, store result in register 8
- "ComprehensionYield { value_reg: 8 }" # Add transformed value to comprehension
- "LoopNext { body_start: 3, loop_end: 6 }" # Continue to next iteration or exit
- "Return { value: 7 }" # Return comprehension collection
want_result: []
- note: array_single_element
description: Array comprehension with single element
example_rego: |
# Transform single element array
[x - 1 | x := [10][_]] # [9]
literals:
- 10
- 1 # subtrahend
instruction_params:
comprehension_begin_params:
- mode: "Array"
collection_reg: 7
key_reg: 4
value_reg: 5
body_start: 4
comprehension_end: 8
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 8
body_start: 5
loop_end: 8
instructions:
- "ArrayNew { dest: 0 }" # Create input array [10] in register 0
- "Load { dest: 1, literal_idx: 0 }" # Load 10 into register 1
- "ArrayPush { arr: 0, value: 1 }" # Push 10 to array
- "ComprehensionBegin { params_index: 0 }" # Start array comprehension and initialize result in register 7
- "LoopStart { params_index: 0 }" # Start loop over array elements
- "Load { dest: 6, literal_idx: 1 }" # Load subtrahend 1 into register 6
- "Sub { dest: 9, left: 5, right: 6 }" # Subtract 1 from current value, store result in register 9
- "ComprehensionYield { value_reg: 9 }" # Add transformed value to comprehension
- "LoopNext { body_start: 5, loop_end: 8 }" # Continue to next iteration or exit
- "Return { value: 7 }" # Return comprehension collection
want_result: [9]
- note: array_with_null_values
description: Array comprehension with null value handling
example_rego: |
# Process array with null values - nulls are preserved
[x | x := [1, null, 3][_]] # [1, null, 3]
literals:
- 1
- 3
instruction_params:
comprehension_begin_params:
- mode: "Array"
collection_reg: 7
key_reg: 4
value_reg: 5
body_start: 8
comprehension_end: 11
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 8
body_start: 9
loop_end: 11
instructions:
- "ArrayNew { dest: 0 }" # Create input array in register 0
- "Load { dest: 1, literal_idx: 0 }" # Load 1
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
- "LoadNull { dest: 2 }" # Load null value
- "ArrayPush { arr: 0, value: 2 }" # Push null to array
- "Load { dest: 3, literal_idx: 1 }" # Load 3
- "ArrayPush { arr: 0, value: 3 }" # Push 3 to array
- "ComprehensionBegin { params_index: 0 }" # Start array comprehension and initialize result in register 7
- "LoopStart { params_index: 0 }" # Start loop over array elements
- "ComprehensionYield { value_reg: 5 }" # Add current value (including null) to comprehension
- "LoopNext { body_start: 9, loop_end: 11 }" # Continue to next iteration or exit
- "Return { value: 7 }" # Return comprehension collection
want_result: [1, null, 3]

View File

@@ -0,0 +1,295 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Empty Collections Loop Test Suite
# Tests behavior of various loop types over empty collections
# 1. Comprehensions should evaluate to their empty versions
# 2. some..in should evaluate to false
# 3. every should evaluate to true
cases:
- note: existential_empty_array
description: Existential quantification (some) on empty array should return false
example_rego: |
# some x in []
# x > 0 # false - no elements to satisfy condition
literals:
- {}
- 0 # comparison value
instruction_params:
loop_params:
- mode: "Any"
collection: 0
key_reg: 4
value_reg: 5
body_start: 2
loop_end: 6
result_reg: 6
instructions:
- "ArrayNew { dest: 0 }" # Create empty array in register 0
- "LoopStart { params_index: 0 }"
- "Load { dest: 7, literal_idx: 1 }" # Load comparison value 0
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 0
- "AssertCondition { condition: 8 }" # Assert the condition
- "LoopNext { body_start: 2, loop_end: 6 }"
- "Return { value: 6 }" # Return false for empty collection
want_result: false
- note: universal_empty_array
description: Universal quantification (every) on empty array should return true
example_rego: |
# every x in []
# x > 0 # true - vacuously true (no elements to violate condition)
literals:
- {}
- 0 # comparison value
instruction_params:
loop_params:
- mode: "Every"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 6
body_start: 2
loop_end: 6
instructions:
- "ArrayNew { dest: 0 }" # Create empty array in register 0
- "LoopStart { params_index: 0 }"
- "Load { dest: 7, literal_idx: 1 }" # Load comparison value 0
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 0
- "AssertCondition { condition: 8 }" # Assert the condition
- "LoopNext { body_start: 2, loop_end: 6 }"
- "Return { value: 6 }" # Return true for empty collection (vacuously true)
want_result: true
- note: array_comprehension_empty
description: Array comprehension on empty collection should return empty array
example_rego: |
# [x + 1 | x = []; true] # []
# Transform each element by adding 1
literals:
- {}
- 1 # value to add
instruction_params:
comprehension_begin_params:
- mode: "Array"
collection_reg: 1
key_reg: 4
value_reg: 5
body_start: 3
comprehension_end: 9
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 8
body_start: 4
loop_end: 9
instructions:
- "ArrayNew { dest: 0 }" # Create empty input array in register 0
- "ComprehensionBegin { params_index: 0 }" # Start array comprehension
- "LoopStart { params_index: 0 }" # Start loop over array elements
- "Load { dest: 6, literal_idx: 1 }" # Load 1 into register 6
- "Add { dest: 7, left: 5, right: 6 }" # Add 1 to current value
- "ComprehensionYield { value_reg: 7 }" # Add result to comprehension
- "LoadBool { dest: 8, value: true }" # Load true (condition always passes)
- "AssertCondition { condition: 8 }" # Assert true condition
- "LoopNext { body_start: 4, loop_end: 9 }" # Continue to next iteration or exit
- "Return { value: 1 }" # Return the result array (should be empty)
want_result: []
- note: set_comprehension_empty
description: Set comprehension on empty collection should return empty set
example_rego: |
# {x + 1 | x = []; true} # set()
# Transform each element by adding 1 into a set
literals:
- {}
- 1 # value to add
instruction_params:
comprehension_begin_params:
- mode: "Set"
collection_reg: 1
key_reg: 4
value_reg: 5
body_start: 3
comprehension_end: 9
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 8
body_start: 4
loop_end: 9
instructions:
- "ArrayNew { dest: 0 }" # Create empty input array in register 0
- "ComprehensionBegin { params_index: 0 }" # Start set comprehension
- "LoopStart { params_index: 0 }" # Start loop over array elements
- "Load { dest: 6, literal_idx: 1 }" # Load 1 into register 6
- "Add { dest: 7, left: 5, right: 6 }" # Add 1 to current value
- "ComprehensionYield { value_reg: 7 }" # Add result to comprehension
- "LoadBool { dest: 8, value: true }" # Load true (condition always passes)
- "AssertCondition { condition: 8 }" # Assert true condition
- "LoopNext { body_start: 4, loop_end: 9 }" # Continue to next iteration or exit
- "Return { value: 1 }" # Return the result set (should be empty)
want_result:
set!: [] # Set serializes as empty array
- note: object_comprehension_empty
description: Object comprehension on empty collection should return empty object
example_rego: |
# {k: v + 1 | some k, v in {}; true} # {}
# Transform each key-value pair
literals:
- {}
- 1 # value to add
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
comprehension_begin_params:
- mode: "Object"
collection_reg: 1
key_reg: 4
value_reg: 5
body_start: 3
comprehension_end: 9
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 8
body_start: 4
loop_end: 9
instructions:
- "ObjectCreate { params_index: 0 }"
- "ComprehensionBegin { params_index: 0 }" # Start object comprehension
- "LoopStart { params_index: 0 }" # Start loop over object elements
- "Load { dest: 6, literal_idx: 1 }" # Load 1 into register 6
- "Add { dest: 7, left: 5, right: 6 }" # Add 1 to current value
- "ComprehensionYield { value_reg: 7 }" # Add result to comprehension (object comprehension needs special handling)
- "LoadBool { dest: 8, value: true }" # Load true (condition always passes)
- "AssertCondition { condition: 8 }" # Assert true condition
- "LoopNext { body_start: 4, loop_end: 9 }" # Continue to next iteration or exit
- "Return { value: 1 }" # Return the result object (should be empty)
want_result: {}
- note: existential_empty_set
description: Existential quantification on empty set should return false
example_rego: |
# some x in set()
# x > 0 # false - no elements in set
literals:
- {}
- 0 # comparison value
instruction_params:
loop_params:
- mode: "Any"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 6
body_start: 2
loop_end: 6
instructions:
- "SetNew { dest: 0 }" # Create empty set in register 0
- "LoopStart { params_index: 0 }"
- "Load { dest: 7, literal_idx: 1 }" # Load comparison value 0
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 0
- "AssertCondition { condition: 8 }" # Assert the condition
- "LoopNext { body_start: 2, loop_end: 6 }"
- "Return { value: 6 }" # Return false for empty set
want_result: false
- note: universal_empty_object
description: Universal quantification on empty object should return true
example_rego: |
# every k, v in {}
# v > 0 # true - vacuously true (no key-value pairs to violate condition)
literals:
- {}
- 0 # comparison value
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
loop_params:
- mode: "Every"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 6
body_start: 2
loop_end: 6
instructions:
- "ObjectCreate { params_index: 0 }"
- "LoopStart { params_index: 0 }"
- "Load { dest: 7, literal_idx: 1 }" # Load comparison value 0
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 0
- "AssertCondition { condition: 8 }" # Assert the condition
- "LoopNext { body_start: 2, loop_end: 6 }"
- "Return { value: 6 }" # Return true for empty object (vacuously true)
want_result: true
- note: nested_empty_comprehensions
description: Nested comprehensions with empty collections
example_rego: |
# [[y | y = []; true] | x = []; true] # []
# Nested array comprehension where both inner and outer collections are empty
literals: []
instruction_params:
comprehension_begin_params:
- mode: "Array"
collection_reg: 0
key_reg: 2
value_reg: 3
body_start: 3
comprehension_end: 16
- mode: "Array"
collection_reg: 6
key_reg: 8
value_reg: 9
body_start: 7
comprehension_end: 12
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 2
value_reg: 3
result_reg: 10
body_start: 4
loop_end: 16
- mode: "ForEach"
collection: 6
key_reg: 8
value_reg: 9
result_reg: 11
body_start: 8
loop_end: 12
instructions:
- "ArrayNew { dest: 0 }" # Create empty outer array in register 0
- "ArrayNew { dest: 1 }" # Create empty result array in register 1
- "ComprehensionBegin { params_index: 0 }"
- "LoopStart { params_index: 0 }" # Start outer loop
# Inner array comprehension (for each x in outer empty array)
- "ArrayNew { dest: 6 }" # Create empty inner array in register 6
- "ArrayNew { dest: 7 }" # Create result for inner comprehension in register 7
- "ComprehensionBegin { params_index: 1 }"
- "LoopStart { params_index: 1 }" # Start inner loop
- "ComprehensionYield { value_reg: 9 }" # Push inner value to inner result (never executes)
- "LoadBool { dest: 10, value: true }" # Load true
- "AssertCondition { condition: 10 }" # Assert true condition for inner loop
- "LoopNext { body_start: 8, loop_end: 12 }" # Continue inner loop
- "ComprehensionYield { value_reg: 7 }" # Push inner result to outer result
- "LoadBool { dest: 11, value: true }" # Load true
- "AssertCondition { condition: 11 }" # Assert true condition for outer loop
- "LoopNext { body_start: 4, loop_end: 16 }" # Continue outer loop
- "Return { value: 1 }" # Return the nested result (should be empty)
want_result: []

View File

@@ -0,0 +1,174 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Existential Loops Test Suite (some)
# Tests existential quantification loops - succeed if ANY element satisfies the condition
# Corresponds to Rego's "some x in collection; condition" patterns
cases:
- note: existential_basic_some
description: Basic existential quantification - some element satisfies condition
example_rego: |
# Check if any element in array is greater than 2
some x in [1, 2, 3]
x > 2 # true (3 > 2)
literals:
- 1
- 2
- 3
- 2 # comparison value
instruction_params:
loop_params:
- mode: "Existential"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 6
body_start: 8
loop_end: 12
instructions:
- "ArrayNew { dest: 0 }" # Create input array [1, 2, 3] in register 0
- "Load { dest: 1, literal_idx: 0 }" # Load 1 into register 1
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
- "Load { dest: 2, literal_idx: 1 }" # Load 2 into register 2
- "ArrayPush { arr: 0, value: 2 }" # Push 2 to array
- "Load { dest: 3, literal_idx: 2 }" # Load 3 into register 3
- "ArrayPush { arr: 0, value: 3 }" # Push 3 to array
- "LoopStart { params_index: 0 }" # Start existential loop using parameter table index 0
- "Load { dest: 7, literal_idx: 3 }" # Load comparison value 2 into register 7
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 2
- "AssertCondition { condition: 8 }" # Assert the condition result for existential logic
- "LoopNext { body_start: 8, loop_end: 12 }" # Continue to next iteration or exit early if condition met
- "Return { value: 6 }" # Return result (true if any element satisfied condition)
want_result: true
- note: existential_none_satisfy
description: Existential quantification where no element satisfies condition
example_rego: |
# Check if any element in array is greater than 5
some x in [1, 2]
x > 5 # false (no element > 5)
literals:
- 1
- 2
- 5 # comparison value
instruction_params:
loop_params:
- mode: "Existential"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 6
body_start: 6
loop_end: 10
instructions:
- "ArrayNew { dest: 0 }" # Create input array [1, 2] in register 0
- "Load { dest: 1, literal_idx: 0 }" # Load 1 into register 1
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
- "Load { dest: 2, literal_idx: 1 }" # Load 2 into register 2
- "ArrayPush { arr: 0, value: 2 }" # Push 2 to array
- "LoopStart { params_index: 0 }" # Start existential loop using parameter table index 0
- "Load { dest: 7, literal_idx: 2 }" # Load comparison value 5 into register 7
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 5
- "AssertCondition { condition: 8 }" # Assert the condition result for existential logic
- "LoopNext { body_start: 6, loop_end: 10 }" # Continue to next iteration
- "Return { value: 6 }" # Return result (false since no element satisfied condition)
want_result: false
- note: existential_empty_collection
description: Existential quantification on empty collection
example_rego: |
# Check if any element in empty array satisfies condition
some x in []
x > 0 # false (no elements to check)
literals:
- 0 # comparison value
instruction_params:
loop_params:
- mode: "Existential"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 6
body_start: 2
loop_end: 6
instructions:
- "ArrayNew { dest: 0 }" # Create empty input array in register 0
- "LoopStart { params_index: 0 }" # Start existential loop using parameter table index 0
- "Load { dest: 7, literal_idx: 0 }" # Load comparison value 0 into register 7
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 0
- "AssertCondition { condition: 8 }" # Assert the condition result for existential logic
- "LoopNext { body_start: 2, loop_end: 6 }" # Continue to next iteration
- "Return { value: 6 }" # Return result (false for empty collection)
want_result: false
- note: existential_simplified_arrays
description: Existential quantification with simple array test
example_rego: |
# Check if any element in array is greater than 5
# Simplified version: check if [3, 7, 4] contains element > 5
some x in [3, 7, 4]
x > 5 # true (7 > 5)
literals:
- 3
- 7
- 4
- 5 # comparison value
instruction_params:
loop_params:
- mode: "Existential"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 6
body_start: 8
loop_end: 12
instructions:
- "ArrayNew { dest: 0 }" # Create array [3, 7, 4] in register 0
- "Load { dest: 1, literal_idx: 0 }" # Load 3
- "ArrayPush { arr: 0, value: 1 }" # Push 3 to array
- "Load { dest: 2, literal_idx: 1 }" # Load 7
- "ArrayPush { arr: 0, value: 2 }" # Push 7 to array
- "Load { dest: 3, literal_idx: 2 }" # Load 4
- "ArrayPush { arr: 0, value: 3 }" # Push 4 to array
- "LoopStart { params_index: 0 }" # Start existential loop using parameter table index 0
- "Load { dest: 7, literal_idx: 3 }" # Load comparison value 5
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 5
- "AssertCondition { condition: 8 }" # Assert the condition for existential logic
- "LoopNext { body_start: 8, loop_end: 12 }" # Continue to next iteration
- "Return { value: 6 }" # Return result
want_result: true
- note: some_basic_failure
description: Basic existential loop that fails
example_rego: "some x in [1, 2, 3]; x > 5" # false because no element > 5
literals:
- 1
- 2
- 3
- 5 # comparison value
instruction_params:
loop_params:
- mode: "Existential"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 6
body_start: 8
loop_end: 12
instructions:
- "ArrayNew { dest: 0 }" # Create array [1, 2, 3] in register 0
- "Load { dest: 1, literal_idx: 0 }" # Load 1 into register 1
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
- "Load { dest: 2, literal_idx: 1 }" # Load 2 into register 2
- "ArrayPush { arr: 0, value: 2 }" # Push 2 to array
- "Load { dest: 3, literal_idx: 2 }" # Load 3 into register 3
- "ArrayPush { arr: 0, value: 3 }" # Push 3 to array
- "LoopStart { params_index: 0 }" # Start existential loop using parameter table index 0
- "Load { dest: 7, literal_idx: 3 }" # Load comparison value 5 into register 7
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 5, store result in register 8
- "AssertCondition { condition: 8 }" # Assert the condition (fails for all elements)
- "LoopNext { body_start: 8, loop_end: 12 }" # Continue to next iteration or exit
- "Return { value: 6 }" # Return boolean result from loop
want_result: false

View File

@@ -0,0 +1,223 @@
# Advanced Loop and Comprehension Interaction Suite
# Exercises nested quantifiers inside comprehensions and object key/value emission.
cases:
- note: array_comprehension_filters_with_inner_any
description: Array comprehension keeps only members whose nested array passes an Any loop
example_rego: |
[arr |
arr := [[1, 0], [1, 2]][_];
some v in arr; v == 0
]
literals:
- 1
- 0
- 2
instruction_params:
comprehension_begin_params:
- mode: "Array"
collection_reg: 7
result_reg: 7
key_reg: 10
value_reg: 11
body_start: 14
comprehension_end: 23
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 10
value_reg: 11
result_reg: 12
body_start: 15
loop_end: 23
- mode: "Any"
collection: 11
key_reg: 13
value_reg: 14
result_reg: 15
body_start: 16
loop_end: 21
instructions:
- "ArrayNew { dest: 0 }"
- "ArrayNew { dest: 1 }"
- "Load { dest: 2, literal_idx: 0 }"
- "ArrayPush { arr: 1, value: 2 }"
- "Load { dest: 3, literal_idx: 1 }"
- "ArrayPush { arr: 1, value: 3 }"
- "ArrayPush { arr: 0, value: 1 }"
- "ArrayNew { dest: 4 }"
- "Load { dest: 5, literal_idx: 0 }"
- "ArrayPush { arr: 4, value: 5 }"
- "Load { dest: 6, literal_idx: 2 }"
- "ArrayPush { arr: 4, value: 6 }"
- "ArrayPush { arr: 0, value: 4 }"
- "ComprehensionBegin { params_index: 0 }"
- "LoopStart { params_index: 0 }"
- "LoopStart { params_index: 1 }"
- "Load { dest: 16, literal_idx: 1 }"
- "Eq { dest: 17, left: 14, right: 16 }"
- "AssertCondition { condition: 17 }"
- "ComprehensionYield { value_reg: 11 }"
- "LoopNext { body_start: 16, loop_end: 21 }"
- "LoopNext { body_start: 15, loop_end: 23 }"
- "ComprehensionEnd"
- "Return { value: 7 }"
want_result:
- [1, 0]
- note: array_comprehension_requires_inner_every
description: Array comprehension keeps only members whose nested array passes an Every loop
example_rego: |
[arr |
arr := [[1, 1], [-1, 2], [2, 3]][_];
every v in arr; v > 0
]
literals:
- 1
- -1
- 2
- 3
- 0
instruction_params:
comprehension_begin_params:
- mode: "Array"
collection_reg: 9
result_reg: 9
key_reg: 10
value_reg: 11
body_start: 20
comprehension_end: 29
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 10
value_reg: 11
result_reg: 12
body_start: 20
loop_end: 29
- mode: "ForEach"
collection: 11
key_reg: 13
value_reg: 14
result_reg: 18
body_start: 22
loop_end: 26
instructions:
- "ArrayNew { dest: 0 }"
- "ArrayNew { dest: 1 }"
- "Load { dest: 2, literal_idx: 0 }"
- "ArrayPush { arr: 1, value: 2 }"
- "ArrayPush { arr: 1, value: 2 }"
- "ArrayPush { arr: 0, value: 1 }"
- "ArrayNew { dest: 3 }"
- "Load { dest: 4, literal_idx: 1 }"
- "ArrayPush { arr: 3, value: 4 }"
- "Load { dest: 5, literal_idx: 2 }"
- "ArrayPush { arr: 3, value: 5 }"
- "ArrayPush { arr: 0, value: 3 }"
- "ArrayNew { dest: 6 }"
- "Load { dest: 7, literal_idx: 2 }"
- "ArrayPush { arr: 6, value: 7 }"
- "Load { dest: 8, literal_idx: 3 }"
- "ArrayPush { arr: 6, value: 8 }"
- "ArrayPush { arr: 0, value: 6 }"
- "ComprehensionBegin { params_index: 0 }"
- "LoopStart { params_index: 0 }"
- "LoadTrue { dest: 15 }"
- "LoopStart { params_index: 1 }"
- "Load { dest: 16, literal_idx: 4 }"
- "Gt { dest: 17, left: 14, right: 16 }"
- "And { dest: 15, left: 15, right: 17 }"
- "LoopNext { body_start: 22, loop_end: 26 }"
- "AssertCondition { condition: 15 }"
- "ComprehensionYield { value_reg: 11 }"
- "LoopNext { body_start: 20, loop_end: 29 }"
- "ComprehensionEnd"
- "Return { value: 9 }"
want_result:
- [1, 1]
- [2, 3]
- note: object_comprehension_with_nested_any
description: Object comprehension emits keys only when nested Any loop succeeds
example_rego: |
{ entry[0]: true |
entry := [["a", [1, 2]], ["b", [1]]][_];
some v in entry[1]; v % 2 == 0
}
literals:
- {}
- "a"
- 1
- 2
- "b"
- 0
instruction_params:
object_create_params:
- dest: 12
template_literal_idx: 0
literal_key_fields: []
fields: []
comprehension_begin_params:
- mode: "Object"
collection_reg: 12
result_reg: 12
key_reg: 17
value_reg: 22
body_start: 21
comprehension_end: 36
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 13
value_reg: 14
result_reg: 15
body_start: 22
loop_end: 36
- mode: "Any"
collection: 19
key_reg: 20
value_reg: 21
result_reg: 22
body_start: 27
loop_end: 33
instructions:
- "ArrayNew { dest: 0 }"
- "ArrayNew { dest: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "ArrayPush { arr: 1, value: 2 }"
- "ArrayNew { dest: 3 }"
- "Load { dest: 4, literal_idx: 2 }"
- "ArrayPush { arr: 3, value: 4 }"
- "Load { dest: 5, literal_idx: 3 }"
- "ArrayPush { arr: 3, value: 5 }"
- "ArrayPush { arr: 1, value: 3 }"
- "ArrayPush { arr: 0, value: 1 }"
- "ArrayNew { dest: 6 }"
- "Load { dest: 7, literal_idx: 4 }"
- "ArrayPush { arr: 6, value: 7 }"
- "ArrayNew { dest: 8 }"
- "Load { dest: 9, literal_idx: 2 }"
- "ArrayPush { arr: 8, value: 9 }"
- "ArrayPush { arr: 6, value: 8 }"
- "ArrayPush { arr: 0, value: 6 }"
- "ObjectCreate { params_index: 0 }"
- "ComprehensionBegin { params_index: 0 }"
- "LoopStart { params_index: 0 }"
- "Load { dest: 16, literal_idx: 5 }"
- "Index { dest: 17, container: 14, key: 16 }"
- "Load { dest: 18, literal_idx: 2 }"
- "Index { dest: 19, container: 14, key: 18 }"
- "LoopStart { params_index: 1 }"
- "Load { dest: 23, literal_idx: 3 }"
- "Mod { dest: 24, left: 21, right: 23 }"
- "Load { dest: 25, literal_idx: 5 }"
- "Eq { dest: 26, left: 24, right: 25 }"
- "AssertCondition { condition: 26 }"
- "LoopNext { body_start: 27, loop_end: 33 }"
- "AssertCondition { condition: 22 }"
- "ComprehensionYield { value_reg: 22, key_reg: 17 }"
- "LoopNext { body_start: 22, loop_end: 36 }"
- "ComprehensionEnd"
- "Return { value: 12 }"
want_result: {"a": true}

View File

@@ -0,0 +1,183 @@
name: "Nested Loops Test Suite"
description: "Test various combinations and levels of nesting for different looping constructs"
cases:
- note: "simple_nested_comprehension"
description: "Test simple nested array comprehension"
example_rego: |
[[x | x := [1, 2][_]] | _ := [1, 2][_]]
literals:
- 1
- 2
instruction_params:
comprehension_begin_params:
- mode: "Array"
collection_reg: 1
key_reg: 2
value_reg: 3
body_start: 6
comprehension_end: 23
- mode: "Array"
collection_reg: 11
key_reg: 12
value_reg: 13
body_start: 12
comprehension_end: 20
loop_params:
- mode: "ForEach"
collection: 6
key_reg: 7
value_reg: 8
result_reg: 9
body_start: 7
loop_end: 22
- mode: "ForEach"
collection: 16
key_reg: 17
value_reg: 18
result_reg: 19
body_start: 15
loop_end: 18
instructions:
- "ComprehensionBegin { params_index: 0 }" # array comprehension in r1, body: 4-21 (P0)
- "Load { dest: 4, literal_idx: 0 }" # Load literal: 1
- "Load { dest: 5, literal_idx: 1 }" # Load literal: 2
- "ArrayNew { dest: 6 }" # Create empty array r6
- "ArrayPush { arr: 6, value: 4 }" # Push r4 to r6
- "ArrayPush { arr: 6, value: 5 }" # Push r5 to r6 -> [1, 2]
- "LoopStart { params_index: 0 }" # foreach loop over r6, body: 8-20 (P0)
# Outer loop body starts here (index 7)
- "Move { dest: 10, src: 8 }" # Copy value from r8 to r10
- "ComprehensionBegin { params_index: 1 }" # array comprehension in r11, body: 10-18 (P1)
- "Load { dest: 14, literal_idx: 0 }" # Load literal: 1
- "Load { dest: 15, literal_idx: 1 }" # Load literal: 2
- "ArrayNew { dest: 16 }" # Create empty array r16
- "ArrayPush { arr: 16, value: 14 }" # Push r14 to r16
- "ArrayPush { arr: 16, value: 15 }" # Push r15 to r16 -> [1, 2]
- "LoopStart { params_index: 1 }" # foreach loop over r16, body: 14-17 (P1)
# Inner loop body starts here (index 16)
- "Move { dest: 20, src: 18 }" # Copy value from r18 to r20
- "ComprehensionYield { value_reg: 20 }" # Yield value to comprehension
- "LoopNext { body_start: 16, loop_end: 19 }" # continue → 16 or exit → 19
# Inner comprehension end (index 19)
- "ComprehensionEnd" # End comprehension block
- "ComprehensionYield { value_reg: 11 }" # Yield value to comprehension
- "LoopNext { body_start: 7, loop_end: 22 }" # continue → 7 or exit → 22
# Outer comprehension end (index 22)
- "ComprehensionEnd" # End comprehension block
- "Move { dest: 0, src: 1 }" # Copy value from r1 to r0
- "Return { value: 0 }" # Return value from r0
want_result: [[1, 2], [1, 2]]
- note: "some_nested_comprehension"
description: "Test some with nested array comprehension"
example_rego: |
[1, 2][_] == [x | x := [1, 2, 3][_]; x > 1][_]
literals:
- 1
- 2
- 3
instruction_params:
comprehension_begin_params:
- mode: "Array"
collection_reg: 12
result_reg: 12
key_reg: 10
value_reg: 11
body_start: 16
comprehension_end: 20
loop_params:
- mode: "Any"
collection: 0
key_reg: 7
value_reg: 8
result_reg: 9
body_start: 14
loop_end: 27
- mode: "ForEach"
collection: 3
key_reg: 10
value_reg: 11
result_reg: 21
body_start: 16
loop_end: 20
- mode: "Any"
collection: 12
key_reg: 13
value_reg: 14
result_reg: 15
body_start: 22
loop_end: 25
instructions:
- "ArrayNew { dest: 0 }" # Index 0: Build left array [1, 2]
- "Load { dest: 1, literal_idx: 0 }" # Index 1: Load literal 1
- "ArrayPush { arr: 0, value: 1 }" # Index 2
- "Load { dest: 2, literal_idx: 1 }" # Index 3: Load literal 2
- "ArrayPush { arr: 0, value: 2 }" # Index 4
- "ArrayNew { dest: 3 }" # Index 5: Build source array [1, 2, 3]
- "Load { dest: 4, literal_idx: 0 }" # Index 6
- "ArrayPush { arr: 3, value: 4 }" # Index 7
- "Load { dest: 5, literal_idx: 1 }" # Index 8
- "ArrayPush { arr: 3, value: 5 }" # Index 9
- "Load { dest: 6, literal_idx: 2 }" # Index 10
- "ArrayPush { arr: 3, value: 6 }" # Index 11
- "Load { dest: 16, literal_idx: 0 }" # Index 12: Load 1 for comparison (stays stable)
- "LoopStart { params_index: 0 }" # Index 13: Start outer some loop over [1, 2]
- "ComprehensionBegin { params_index: 0 }" # Index 14: Build filtered comprehension
- "LoopStart { params_index: 1 }" # Index 15: Iterate source values for comprehension
- "Gt { dest: 17, left: 11, right: 16 }" # Index 16: Check x > 1
- "AssertCondition { condition: 17 }" # Index 17: Skip values <= 1
- "ComprehensionYield { value_reg: 11 }" # Index 18: Include qualifying value
- "LoopNext { body_start: 16, loop_end: 20 }" # Index 19: Continue comprehension loop
- "ComprehensionEnd" # Index 20: Finish comprehension block
- "LoopStart { params_index: 2 }" # Index 21: Iterate comprehension results
- "Eq { dest: 18, left: 8, right: 14 }" # Index 22: Compare outer value with result value
- "AssertCondition { condition: 18 }" # Index 23: Success when values match
- "LoopNext { body_start: 22, loop_end: 25 }" # Index 24: Continue inner any loop
- "AssertCondition { condition: 15 }" # Index 25: Require some match from inner any loop
- "LoopNext { body_start: 14, loop_end: 27 }" # Index 26: Continue outer some loop
- "Return { value: 9 }" # Index 27
want_result: true
- note: "nested_with_condition"
description: "Test nested loops with conditional logic"
example_rego: |
[x | x := [1, 2, 3][_]; x > 1]
literals:
- 1
- 2
- 3
instruction_params:
comprehension_begin_params:
- mode: "Array"
collection_reg: 6
result_reg: 6
key_reg: 4
value_reg: 5
body_start: 9
comprehension_end: 14
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 7
body_start: 9
loop_end: 14
instructions:
- "ArrayNew { dest: 0 }" # Index 0: Create input array [1, 2, 3]
- "Load { dest: 1, literal_idx: 0 }" # Index 1: Load 1
- "ArrayPush { arr: 0, value: 1 }" # Index 2: Push 1 to array
- "Load { dest: 2, literal_idx: 1 }" # Index 3: Load 2
- "ArrayPush { arr: 0, value: 2 }" # Index 4: Push 2 to array
- "Load { dest: 3, literal_idx: 2 }" # Index 5: Load 3
- "ArrayPush { arr: 0, value: 3 }" # Index 6: Push 3 to array
- "ComprehensionBegin { params_index: 0 }" # Index 7: Start comprehension
- "LoopStart { params_index: 0 }" # Index 8: Start loop
- "Load { dest: 7, literal_idx: 0 }" # Index 9: Load 1 for comparison
- "Gt { dest: 8, left: 5, right: 7 }" # Index 10: x > 1
- "AssertCondition { condition: 8 }" # Index 11: Assert x > 1 (skip if false)
- "ComprehensionYield { value_reg: 5 }" # Index 12: Push x to result if condition true
- "LoopNext { body_start: 9, loop_end: 13 }" # Index 13: Continue loop
- "Return { value: 6 }" # Index 14: Return comprehension result
want_result: [2, 3]

View File

@@ -0,0 +1,7 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Placeholder for nested fixed loops - currently empty
# TODO: Add test cases for fixed nested loop scenarios
cases: []

View File

@@ -0,0 +1,192 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Object Comprehension Test Suite
# Tests object comprehensions - construct objects with key-value pairs based on conditions
# Corresponds to Rego's "{key: value | condition}" patterns
cases:
- note: object_simple_key_value
description: Simple object comprehension with computed values
example_rego: |
# Create object mapping each value to its double
{x: x * 2 | x := [1, 2, 3][_]} # {1: 2, 2: 4, 3: 6}
literals:
- {}
- 1
- 2
- 3
- 2 # multiplier
instruction_params:
object_create_params:
- dest: 9
template_literal_idx: 0
literal_key_fields: []
fields: []
comprehension_begin_params:
- mode: "Object"
collection_reg: 9
result_reg: 9
key_reg: 4
value_reg: 5
body_start: 10
comprehension_end: 13
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 10
body_start: 10
loop_end: 14
instructions:
- "ArrayNew { dest: 0 }" # Index 0: Create input array [1, 2, 3]
- "Load { dest: 1, literal_idx: 1 }" # Index 1: Load 1
- "ArrayPush { arr: 0, value: 1 }" # Index 2
- "Load { dest: 2, literal_idx: 2 }" # Index 3: Load 2
- "ArrayPush { arr: 0, value: 2 }" # Index 4
- "Load { dest: 3, literal_idx: 3 }" # Index 5: Load 3
- "ArrayPush { arr: 0, value: 3 }" # Index 6
- "ObjectCreate { params_index: 0 }" # Index 7: Create empty result object in r9
- "ComprehensionBegin { params_index: 0 }" # Index 8: Start object comprehension
- "LoopStart { params_index: 0 }" # Index 9: Start loop
- "Load { dest: 11, literal_idx: 4 }" # Index 10: Load multiplier 2
- "Mul { dest: 12, left: 5, right: 11 }" # Index 11: Multiply value by 2
- "ComprehensionYield { value_reg: 12, key_reg: 5 }" # Index 12: Add key-value pair
- "LoopNext { body_start: 10, loop_end: 14 }" # Index 13: Continue loop
- "Return { value: 9 }" # Index 14: Return result object
want_result: {1: 2, 2: 4, 3: 6}
- note: object_empty_input
description: Object comprehension with empty input
example_rego: |
# Create object from empty array
{x: x + 5 | x := [][_]} # {} (empty object)
literals:
- {}
- 5 # addend
instruction_params:
object_create_params:
- dest: 9
template_literal_idx: 0
literal_key_fields: []
fields: []
comprehension_begin_params:
- mode: "Object"
collection_reg: 9
result_reg: 9
key_reg: 4
value_reg: 5
body_start: 4
comprehension_end: 7
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 10
body_start: 4
loop_end: 8
instructions:
- "ArrayNew { dest: 0 }" # Index 0: Create empty input array
- "ObjectCreate { params_index: 0 }" # Index 1: Create empty result object in r9
- "ComprehensionBegin { params_index: 0 }" # Index 2: Start object comprehension
- "LoopStart { params_index: 0 }" # Index 3: Start loop
- "Load { dest: 10, literal_idx: 1 }" # Index 4: Load addend 5
- "Add { dest: 11, left: 5, right: 10 }" # Index 5: Add 5 to value
- "ComprehensionYield { value_reg: 11, key_reg: 5 }" # Index 6: Add key-value pair
- "LoopNext { body_start: 4, loop_end: 8 }" # Index 7: Continue loop
- "Return { value: 9 }" # Index 8: Return result object
want_result: {}
- note: object_single_element
description: Object comprehension with single element
example_rego: |
# Create object with single key-value pair
{x: x - 1 | x := [5][_]} # {5: 4}
literals:
- {}
- 5
- 1 # subtrahend
instruction_params:
object_create_params:
- dest: 9
template_literal_idx: 0
literal_key_fields: []
fields: []
comprehension_begin_params:
- mode: "Object"
collection_reg: 9
result_reg: 9
key_reg: 4
value_reg: 5
body_start: 6
comprehension_end: 9
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 10
body_start: 6
loop_end: 10
instructions:
- "ArrayNew { dest: 0 }" # Index 0: Create input array [5]
- "Load { dest: 1, literal_idx: 1 }" # Index 1: Load 5
- "ArrayPush { arr: 0, value: 1 }" # Index 2: Push 5 to array
- "ObjectCreate { params_index: 0 }" # Index 3: Create empty result object in r9
- "ComprehensionBegin { params_index: 0 }" # Index 4: Start object comprehension
- "LoopStart { params_index: 0 }" # Index 5: Start loop
- "Load { dest: 10, literal_idx: 2 }" # Index 6: Load subtrahend 1
- "Sub { dest: 11, left: 5, right: 10 }" # Index 7: Subtract 1 from value
- "ComprehensionYield { value_reg: 11, key_reg: 5 }" # Index 8: Add key-value pair
- "LoopNext { body_start: 6, loop_end: 10 }" # Index 9: Continue loop
- "Return { value: 9 }" # Index 10: Return result object
want_result: {5: 4}
- note: object_with_null_keys
description: Object comprehension with null keys and values
example_rego: |
# Create object with null keys/values
{x: x | x := [1, null, 2][_]} # {1: 1, null: null, 2: 2}
literals:
- {}
- 1
- 2
instruction_params:
object_create_params:
- dest: 9
template_literal_idx: 0
literal_key_fields: []
fields: []
comprehension_begin_params:
- mode: "Object"
collection_reg: 9
result_reg: 9
key_reg: 4
value_reg: 5
body_start: 10
comprehension_end: 11
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 10
body_start: 10
loop_end: 12
instructions:
- "ArrayNew { dest: 0 }" # Index 0: Create input array
- "Load { dest: 1, literal_idx: 1 }" # Index 1: Load 1
- "ArrayPush { arr: 0, value: 1 }" # Index 2: Push 1 to array
- "LoadNull { dest: 2 }" # Index 3: Load null value
- "ArrayPush { arr: 0, value: 2 }" # Index 4: Push null to array
- "Load { dest: 3, literal_idx: 2 }" # Index 5: Load 2
- "ArrayPush { arr: 0, value: 3 }" # Index 6: Push 2 to array
- "ObjectCreate { params_index: 0 }" # Index 7: Create empty result object in r9
- "ComprehensionBegin { params_index: 0 }" # Index 8: Start object comprehension
- "LoopStart { params_index: 0 }" # Index 9: Start loop
- "ComprehensionYield { value_reg: 5, key_reg: 5 }" # Index 10: Use value as both key and value
- "LoopNext { body_start: 10, loop_end: 12 }" # Index 11: Continue loop
- "Return { value: 9 }" # Index 12: Return result object
want_result: {1: 1, null: null, 2: 2}

View File

@@ -0,0 +1,139 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Set Comprehension Test Suite
# Tests set comprehensions - collect unique transformed values based on conditions
# Corresponds to Rego's "{transform | condition}" patterns
cases:
- note: set_simple_transform
description: Simple set comprehension with transformation
example_rego: |
# Transform array values into set - duplicates removed
{x * 2 | x := [1, 2, 2, 3][_]} # {2, 4, 6} (duplicates removed)
literals:
- 1
- 2
- 3
- 2 # multiplier
instruction_params:
comprehension_start_params:
- mode: "Set"
collection_reg: 0
key_reg: 4
value_reg: 5
result_reg: 7
body_start: 10
comprehension_end: 14
instructions:
- "ArrayNew { dest: 0 }" # Create input array [1, 2, 2, 3] in register 0
- "Load { dest: 1, literal_idx: 0 }" # Load 1 into register 1
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
- "Load { dest: 2, literal_idx: 1 }" # Load 2 into register 2
- "ArrayPush { arr: 0, value: 2 }" # Push 2 to array
- "ArrayPush { arr: 0, value: 2 }" # Push 2 again to array (duplicate)
- "Load { dest: 3, literal_idx: 2 }" # Load 3 into register 3
- "ArrayPush { arr: 0, value: 3 }" # Push 3 to array
- "SetNew { dest: 7 }" # Initialize result set in register 7
- "ComprehensionStart { params_index: 0 }" # Start set comprehension
- "Load { dest: 6, literal_idx: 3 }" # Load multiplier 2 into register 6
- "Mul { dest: 10, left: 5, right: 6 }" # Multiply current value by 2, store result in register 10
- "ComprehensionAdd { value_reg: 10 }" # Add transformed value to result set (auto-deduplicates)
- "Halt" # End comprehension
- "Return { value: 7 }" # Return result set
want_result:
set!: [2, 4, 6]
- note: set_empty_input
description: Set comprehension with empty input
example_rego: |
# Transform empty array into set
{x + 5 | x := [][_]} # {} (empty set)
literals:
- 5 # addend
instruction_params:
comprehension_start_params:
- mode: "Set"
collection_reg: 0
key_reg: 4
value_reg: 5
result_reg: 7
body_start: 3
comprehension_end: 7
instructions:
- "ArrayNew { dest: 0 }" # Create empty input array in register 0
- "SetNew { dest: 7 }" # Initialize result set in register 7
- "ComprehensionStart { params_index: 0 }" # Start set comprehension
- "Load { dest: 6, literal_idx: 0 }" # Load addend 5 into register 6
- "Add { dest: 10, left: 5, right: 6 }" # Add 5 to current value, store result in register 10
- "ComprehensionAdd { value_reg: 10 }" # Add transformed value to result set
- "Halt" # End comprehension
- "Return { value: 7 }" # Return result set
want_result:
set!: []
- note: set_single_element
description: Set comprehension with single element
example_rego: |
# Transform single element into set
{x - 1 | x := [10][_]} # {9}
literals:
- 10
- 1 # subtrahend
instruction_params:
comprehension_start_params:
- mode: "Set"
collection_reg: 0
key_reg: 4
value_reg: 5
result_reg: 7
body_start: 5
comprehension_end: 9
instructions:
- "ArrayNew { dest: 0 }" # Create input array [10] in register 0
- "Load { dest: 1, literal_idx: 0 }" # Load 10 into register 1
- "ArrayPush { arr: 0, value: 1 }" # Push 10 to array
- "SetNew { dest: 7 }" # Initialize result set in register 7
- "ComprehensionStart { params_index: 0 }" # Start set comprehension
- "Load { dest: 6, literal_idx: 1 }" # Load subtrahend 1 into register 6
- "Sub { dest: 10, left: 5, right: 6 }" # Subtract 1 from current value, store result in register 10
- "ComprehensionAdd { value_reg: 10 }" # Add transformed value to result set
- "Halt" # End comprehension
- "Return { value: 7 }" # Return result set
want_result:
set!: [9]
- note: set_with_null_deduplication
description: Set comprehension with null values and deduplication
example_rego: |
# Collect unique values including nulls
{x | x := [1, null, 1, null, 2][_]} # {1, null, 2}
literals:
- 1
- 2
instruction_params:
comprehension_start_params:
- mode: "Set"
collection_reg: 0
key_reg: 4
value_reg: 5
result_reg: 7
body_start: 11
comprehension_end: 13
instructions:
- "ArrayNew { dest: 0 }" # Create input array in register 0
- "Load { dest: 1, literal_idx: 0 }" # Load 1
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
- "LoadNull { dest: 2 }" # Load null value
- "ArrayPush { arr: 0, value: 2 }" # Push null to array
- "ArrayPush { arr: 0, value: 1 }" # Push 1 again (duplicate)
- "ArrayPush { arr: 0, value: 2 }" # Push null again (duplicate)
- "Load { dest: 3, literal_idx: 1 }" # Load 2
- "ArrayPush { arr: 0, value: 3 }" # Push 2 to array
- "SetNew { dest: 7 }" # Initialize result set in register 7
- "ComprehensionStart { params_index: 0 }" # Start set comprehension
- "ComprehensionAdd { value_reg: 5 }" # Add current value to result set (auto-deduplicates)
- "Halt" # End comprehension
- "Return { value: 7 }" # Return result set
want_result:
set!: [1, null, 2]

View File

@@ -0,0 +1,143 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Universal Loops Test Suite
# Tests universal quantification - succeed if ALL elements satisfy the condition
# Corresponds to Rego's "every x in collection; condition" patterns
cases:
- note: universal_basic_every
description: Basic universal quantification - every element satisfies condition
example_rego: |
# Check if every element in array is greater than 0
every x in [1, 2, 3] {
x > 0 # true (all elements > 0)
}
literals:
- 1
- 2
- 3
- 0 # comparison value
instruction_params:
loop_params:
- mode: "Every"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 6
body_start: 8
loop_end: 12
instructions:
- "ArrayNew { dest: 0 }" # Create input array [1, 2, 3] in register 0
- "Load { dest: 1, literal_idx: 0 }" # Load 1 into register 1
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
- "Load { dest: 2, literal_idx: 1 }" # Load 2 into register 2
- "ArrayPush { arr: 0, value: 2 }" # Push 2 to array
- "Load { dest: 3, literal_idx: 2 }" # Load 3 into register 3
- "ArrayPush { arr: 0, value: 3 }" # Push 3 to array
- "LoopStart { params_index: 0 }" # Start universal loop using parameter table index 0
- "Load { dest: 7, literal_idx: 3 }" # Load comparison value 0 into register 7
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 0
- "AssertCondition { condition: 8 }" # Assert the condition result for universal logic
- "LoopNext { body_start: 8, loop_end: 12 }" # Continue to next iteration or exit early if condition fails
- "Return { value: 6 }" # Return result (true if all elements satisfied condition)
want_result: true
- note: universal_one_fails
description: Universal quantification where one element fails condition
example_rego: |
# Check if every element in array is greater than 1
every x in [1, 2, 3] {
x > 1 # false (1 is not > 1)
}
literals:
- 1
- 2
- 3
- 1 # comparison value
instruction_params:
loop_params:
- mode: "Every"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 6
body_start: 8
loop_end: 12
instructions:
- "ArrayNew { dest: 0 }" # Create input array [1, 2, 3] in register 0
- "Load { dest: 1, literal_idx: 0 }" # Load 1 into register 1
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
- "Load { dest: 2, literal_idx: 1 }" # Load 2 into register 2
- "ArrayPush { arr: 0, value: 2 }" # Push 2 to array
- "Load { dest: 3, literal_idx: 2 }" # Load 3 into register 3
- "ArrayPush { arr: 0, value: 3 }" # Push 3 to array
- "LoopStart { params_index: 0 }" # Start universal loop using parameter table index 0
- "Load { dest: 7, literal_idx: 3 }" # Load comparison value 1 into register 7
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 1
- "AssertCondition { condition: 8 }" # Assert the condition result for universal logic
- "LoopNext { body_start: 8, loop_end: 12 }" # Continue to next iteration or exit early on failure
- "Return { value: 6 }" # Return result (false since first element failed condition)
want_result: false
- note: universal_empty_collection
description: Universal quantification on empty collection
example_rego: |
# Check if every element in empty array satisfies condition
every x in [] {
x > 0 # true (vacuously true - all 0 elements satisfy condition)
}
literals:
- 0 # comparison value
instruction_params:
loop_params:
- mode: "Every"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 6
body_start: 2
loop_end: 5
instructions:
- "ArrayNew { dest: 0 }" # Create empty input array in register 0
- "LoopStart { params_index: 0 }" # Start universal loop
- "Load { dest: 7, literal_idx: 0 }" # Load comparison value 0 into register 7
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 0
- "LoopNext { body_start: 2, loop_end: 5 }" # Continue to next iteration
- "Return { value: 6 }" # Return result (true for empty collection - vacuous truth)
want_result: true
- note: universal_null_handling
description: Universal quantification with null values
example_rego: |
# Test behavior with null values - should handle gracefully
every x in [2, null, 4] {
x != null # false (null fails the condition)
}
literals:
- 2
- 4
instruction_params:
loop_params:
- mode: "Every"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 6
body_start: 8
loop_end: 12
instructions:
- "ArrayNew { dest: 0 }" # Create input array in register 0
- "Load { dest: 1, literal_idx: 0 }" # Load 2
- "ArrayPush { arr: 0, value: 1 }" # Push 2 to array
- "LoadNull { dest: 2 }" # Load null value
- "ArrayPush { arr: 0, value: 2 }" # Push null to array
- "Load { dest: 3, literal_idx: 1 }" # Load 4
- "ArrayPush { arr: 0, value: 3 }" # Push 4 to array
- "LoopStart { params_index: 0 }" # Start universal loop
- "LoadNull { dest: 7 }" # Load null for comparison
- "Ne { dest: 8, left: 5, right: 7 }" # Check if current value != null
- "AssertCondition { condition: 8 }" # Assert the condition result for universal logic
- "LoopNext { body_start: 8, loop_end: 12 }" # Continue to next iteration
- "Return { value: 6 }" # Return result
want_result: false

View File

@@ -0,0 +1,376 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Null and Undefined Handling Test Suite
# Tests null and undefined behavior across all instruction families
# Covers arithmetic, comparisons, logical ops, indexing, loops, and comprehensions
cases:
- note: load_null_basic
description: LoadNull loads null value
example_rego: "null"
literals: []
instructions:
- "LoadNull { dest: 0 }"
- "Return { value: 0 }"
want_result: null
- note: null_in_arithmetic_add
description: Adding null is a type error
example_rego: "null + 1"
literals:
- 1
instructions:
- "LoadNull { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Add { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_error: "Cannot add Null"
- note: null_in_arithmetic_sub
description: Subtracting null is a type error
example_rego: "5 - null"
literals:
- 5
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "LoadNull { dest: 1 }"
- "Sub { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_error: "Cannot subtract Number(5)"
- note: null_in_arithmetic_mul
description: Multiplying null is a type error
example_rego: "null * 3"
literals:
- 3
instructions:
- "LoadNull { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Mul { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_error: "Cannot multiply Null"
- note: null_in_comparison_eq
description: null equals null
example_rego: "null == null"
literals: []
instructions:
- "LoadNull { dest: 0 }"
- "LoadNull { dest: 1 }"
- "Eq { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: true
- note: null_not_equal_to_number
description: null is not equal to numbers
example_rego: "null == 0"
literals:
- 0
instructions:
- "LoadNull { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Eq { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: false
- note: null_not_equal_to_false
description: null is not equal to false
example_rego: "null == false"
literals: []
instructions:
- "LoadNull { dest: 0 }"
- "LoadFalse { dest: 1 }"
- "Eq { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: false
- note: null_in_comparison_lt
description: Ordering comparison treats null as less than numbers
example_rego: "null < 5"
literals:
- 5
instructions:
- "LoadNull { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Lt { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: true
- note: null_in_logical_and
description: Logical AND treats null as truthy
example_rego: "null && true"
literals: []
instructions:
- "LoadNull { dest: 0 }"
- "LoadTrue { dest: 1 }"
- "And { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: true
- note: null_in_logical_or
description: Logical OR treats null as truthy
example_rego: "null || false"
literals: []
instructions:
- "LoadNull { dest: 0 }"
- "LoadFalse { dest: 1 }"
- "Or { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: true
- note: null_in_logical_not
description: Logical NOT treats null as truthy (returns false)
example_rego: "not null"
literals: []
instructions:
- "LoadNull { dest: 0 }"
- "Not { dest: 1, operand: 0 }"
- "Return { value: 1 }"
want_result: false
- note: null_as_array_index
description: Indexing array with null key
example_rego: "[1, 2, 3][null]"
literals:
- [1, 2, 3]
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "LoadNull { dest: 1 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "Return { value: 2 }"
want_result: "#undefined" # null is not a valid array index
- note: null_as_object_key
description: Indexing object with null key
example_rego: "{\"a\": 1, \"b\": 2}[null]"
literals:
- {"a": 1, "b": 2}
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "LoadNull { dest: 1 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "Return { value: 2 }"
want_result: "#undefined" # null key doesn't exist
- note: null_in_contains_check
description: Contains check with null
example_rego: "null in [1, 2, null, 3]"
literals:
- [1, 2, null, 3]
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "LoadNull { dest: 1 }"
- "Contains { dest: 2, collection: 0, value: 1 }"
- "Return { value: 2 }"
want_result: true
- note: null_in_set
description: null can be a set member
example_rego: "{1, null, 3}"
literals:
- 1
- 3
instructions:
- "SetNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "SetAdd { set: 0, value: 1 }"
- "LoadNull { dest: 2 }"
- "SetAdd { set: 0, value: 2 }"
- "Load { dest: 3, literal_idx: 1 }"
- "SetAdd { set: 0, value: 3 }"
- "Return { value: 0 }"
want_result:
set!:
- 1
- null
- 3
- note: null_in_array
description: null can be an array element
example_rego: "[1, null, 3]"
literals:
- 1
- 3
instruction_params:
array_create_params:
- dest: 0
elements: [1, 2, 3]
instructions:
- "Load { dest: 1, literal_idx: 0 }"
- "LoadNull { dest: 2 }"
- "Load { dest: 3, literal_idx: 1 }"
- "ArrayCreate { params_index: 0 }"
- "Return { value: 0 }"
want_result: [1, null, 3]
- note: null_in_object_value
description: null can be an object value
example_rego: "{\"a\": 1, \"b\": null}"
literals:
- "a"
- 1
- "b"
- {}
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 3
literal_key_fields: []
fields:
- [1, 2]
- [3, 4]
instructions:
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Load { dest: 3, literal_idx: 2 }"
- "LoadNull { dest: 4 }"
- "ObjectCreate { params_index: 0 }"
- "Return { value: 0 }"
want_result: {"a": 1, "b": null}
- note: undefined_in_arithmetic
description: Arithmetic with undefined register produces undefined
example_rego: "undefined_var + 1"
literals:
- 1
instructions:
# r0 is undefined (not loaded)
- "Load { dest: 1, literal_idx: 0 }"
- "Add { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: "#undefined"
- note: undefined_in_comparison
description: Comparison with undefined returns undefined
example_rego: "undefined_var == 5"
literals:
- 5
instructions:
# r0 is undefined
- "Load { dest: 1, literal_idx: 0 }"
- "Eq { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: "#undefined"
- note: undefined_in_logical_ops
description: Logical operations with undefined return undefined
example_rego: "undefined_var && true"
literals: []
instructions:
# r0 is undefined
- "LoadTrue { dest: 1 }"
- "And { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: "#undefined"
- note: undefined_indexing
description: Indexing undefined returns undefined
example_rego: "undefined_var[\"key\"]"
literals:
- "key"
instructions:
# r0 is undefined
- "Load { dest: 1, literal_idx: 0 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "Return { value: 2 }"
want_result: "#undefined"
- note: undefined_in_array_create
description: ArrayCreate with undefined element returns undefined
literals:
- 1
- 2
instruction_params:
array_create_params:
- dest: 0
elements: [1, 2, 3] # r3 is undefined
instructions:
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
# r3 is undefined
- "ArrayCreate { params_index: 0 }"
- "Return { value: 0 }"
want_result: "#undefined"
- note: undefined_in_object_create_key
description: ObjectCreate with undefined key returns undefined
literals:
- 1
- 2
- {}
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 2
literal_key_fields: []
fields:
- [1, 2]
- [3, 4]
instructions:
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
# r3, r4 are undefined
- "ObjectCreate { params_index: 0 }"
- "Return { value: 0 }"
want_result: "#undefined"
- note: undefined_in_object_create_value
description: ObjectCreate with undefined value returns undefined
literals:
- "key1"
- "key2"
- 1
- {}
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 3
literal_key_fields: []
fields:
- [1, 3]
- [2, 4] # r4 is undefined
instructions:
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Load { dest: 3, literal_idx: 2 }"
# r4 is undefined
- "ObjectCreate { params_index: 0 }"
- "Return { value: 0 }"
want_result: "#undefined"
- note: undefined_in_loop_collection
description: Loop over undefined collection fails gracefully
literals: []
instruction_params:
loop_params:
- mode: "ForEach"
collection: 0 # r0 is undefined
key_reg: 1
value_reg: 2
result_reg: 4
body_start: 1
loop_end: 3
instructions:
# r0 is undefined
- "LoopStart { params_index: 0 }"
# Loop body (never executed because collection is undefined)
- "LoadTrue { dest: 3 }"
- "LoopNext { body_start: 1, loop_end: 3 }"
- "Return { value: 3 }"
want_result: "#undefined" # Loop over undefined collection yields undefined
- note: undefined_propagation_through_chain
description: Undefined propagates through operation chain
example_rego: "(undefined_var + 1) * 2"
literals:
- 1
- 2
instructions:
# r0 is undefined
- "Load { dest: 1, literal_idx: 0 }"
- "Add { dest: 2, left: 0, right: 1 }" # r2 becomes undefined
- "Load { dest: 3, literal_idx: 1 }"
- "Mul { dest: 4, left: 2, right: 3 }" # r4 becomes undefined
- "Return { value: 4 }"
want_result: "#undefined"

View File

@@ -0,0 +1,276 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Object Operations Test Suite
# Tests advanced object creation, manipulation, and edge cases
# Covers dynamic keys, collisions, non-string keys, and template validation
cases:
- note: object_key_collision_overwrite
description: Setting same key twice should overwrite the value
example_rego: "{\"key\": 1, \"key\": 2}"
literals:
- {}
- "key"
- 1
- 2
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
instructions:
- "ObjectCreate { params_index: 0 }"
- "Load { dest: 1, literal_idx: 1 }" # key
- "Load { dest: 2, literal_idx: 2 }" # value 1
- "ObjectSet { obj: 0, key: 1, value: 2 }"
- "Load { dest: 3, literal_idx: 3 }" # value 2
- "ObjectSet { obj: 0, key: 1, value: 3 }" # Overwrite
- "Return { value: 0 }"
want_result: {"key": 2}
- note: object_dynamic_key_generation
description: Generate object keys dynamically from loop iteration
example_rego: "{sprintf(\"key_%d\", [i]): i | i := [1, 2][_]}"
literals:
- {}
- 1
- 2
- "key_1"
- "key_2"
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
loop_params:
- mode: "ForEach"
collection: 5
key_reg: 10
value_reg: 11
result_reg: 12
body_start: 7
loop_end: 13
instructions:
- "ObjectCreate { params_index: 0 }"
- "ArrayNew { dest: 5 }"
- "Load { dest: 1, literal_idx: 1 }"
- "ArrayPush { arr: 5, value: 1 }"
- "Load { dest: 2, literal_idx: 2 }"
- "ArrayPush { arr: 5, value: 2 }"
- "LoopStart { params_index: 0 }"
# Generate dynamic key based on value
- "Load { dest: 20, literal_idx: 3 }" # "key_1" when i=1
- "Load { dest: 21, literal_idx: 4 }" # "key_2" when i=2
# For simplicity, we'll set both keys
- "ObjectSet { obj: 0, key: 20, value: 1 }"
- "Load { dest: 3, literal_idx: 2 }"
- "ObjectSet { obj: 0, key: 21, value: 3 }"
- "LoopNext { body_start: 7, loop_end: 13 }"
- "Return { value: 0 }"
want_result: {"key_1": 1, "key_2": 2}
- note: object_number_keys
description: Object with number keys
example_rego: "{1: \"one\", 2: \"two\", 42: \"answer\"}"
literals:
- {}
- 1
- "one"
- 2
- "two"
- 42
- "answer"
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
instructions:
- "ObjectCreate { params_index: 0 }"
- "Load { dest: 1, literal_idx: 1 }" # key 1
- "Load { dest: 2, literal_idx: 2 }" # value "one"
- "ObjectSet { obj: 0, key: 1, value: 2 }"
- "Load { dest: 3, literal_idx: 3 }" # key 2
- "Load { dest: 4, literal_idx: 4 }" # value "two"
- "ObjectSet { obj: 0, key: 3, value: 4 }"
- "Load { dest: 5, literal_idx: 5 }" # key 42
- "Load { dest: 6, literal_idx: 6 }" # value "answer"
- "ObjectSet { obj: 0, key: 5, value: 6 }"
- "Return { value: 0 }"
want_result: {1: "one", 2: "two", 42: "answer"}
- note: object_boolean_keys
description: Object with boolean keys
example_rego: "{true: \"yes\", false: \"no\"}"
literals:
- {}
- "yes"
- "no"
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
instructions:
- "ObjectCreate { params_index: 0 }"
- "LoadTrue { dest: 1 }"
- "Load { dest: 2, literal_idx: 1 }" # value "yes"
- "ObjectSet { obj: 0, key: 1, value: 2 }"
- "LoadFalse { dest: 3 }"
- "Load { dest: 4, literal_idx: 2 }" # value "no"
- "ObjectSet { obj: 0, key: 3, value: 4 }"
- "Return { value: 0 }"
want_result: {true: "yes", false: "no"}
- note: object_null_key
description: Object with null key
example_rego: "{null: \"nothing\"}"
literals:
- {}
- "nothing"
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
instructions:
- "ObjectCreate { params_index: 0 }"
- "LoadNull { dest: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "ObjectSet { obj: 0, key: 1, value: 2 }"
- "Return { value: 0 }"
want_result: {null: "nothing"}
- note: object_empty_string_key
description: Object with empty string key
example_rego: "{\"\": \"empty_key\"}"
literals:
- {}
- ""
- "empty_key"
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
instructions:
- "ObjectCreate { params_index: 0 }"
- "Load { dest: 1, literal_idx: 1 }" # empty string key
- "Load { dest: 2, literal_idx: 2 }" # value
- "ObjectSet { obj: 0, key: 1, value: 2 }"
- "Return { value: 0 }"
want_result: {"": "empty_key"}
- note: object_set_undefined_value
description: Setting undefined value in object keeps it
literals:
- {}
- "key"
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
instructions:
- "ObjectCreate { params_index: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
# r2 is undefined (never loaded)
- "ObjectSet { obj: 0, key: 1, value: 2 }"
- "Return { value: 0 }"
want_result: {"key": "#undefined"}
- note: object_create_with_template
description: ObjectCreate with pre-populated template
literals:
- {"existing": "value", "num": 42}
- "new_key"
- "new_value"
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
instructions:
- "ObjectCreate { params_index: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Load { dest: 2, literal_idx: 2 }"
- "ObjectSet { obj: 0, key: 1, value: 2 }"
- "Return { value: 0 }"
want_result: {"existing": "value", "num": 42, "new_key": "new_value"}
- note: object_nested_structure
description: Create deeply nested object structure
example_rego: "{\"outer\": {\"middle\": {\"inner\": \"value\"}}}"
literals:
- {}
- "outer"
- "middle"
- "inner"
- "value"
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
instructions:
- "ObjectCreate { params_index: 0 }" # outer
- "Load { dest: 10, literal_idx: 0 }" # middle object
- "Load { dest: 11, literal_idx: 0 }" # inner object
- "Load { dest: 12, literal_idx: 3 }" # "inner" key
- "Load { dest: 13, literal_idx: 4 }" # "value"
- "ObjectSet { obj: 11, key: 12, value: 13 }" # {inner: "value"}
- "Load { dest: 14, literal_idx: 2 }" # "middle" key
- "ObjectSet { obj: 10, key: 14, value: 11 }" # {middle: {...}}
- "Load { dest: 15, literal_idx: 1 }" # "outer" key
- "ObjectSet { obj: 0, key: 15, value: 10 }" # {outer: {...}}
- "Return { value: 0 }"
want_result: {"outer": {"middle": {"inner": "value"}}}
- note: object_mixed_value_types
description: Object with values of different types
example_rego: "{\"str\": \"text\", \"num\": 42, \"bool\": true, \"null\": null, \"arr\": [1, 2]}"
literals:
- {}
- "str"
- "text"
- "num"
- 42
- "bool"
- "null"
- "arr"
- [1, 2]
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
instructions:
- "ObjectCreate { params_index: 0 }"
- "Load { dest: 1, literal_idx: 1 }" # "str"
- "Load { dest: 2, literal_idx: 2 }" # "text"
- "ObjectSet { obj: 0, key: 1, value: 2 }"
- "Load { dest: 3, literal_idx: 3 }" # "num"
- "Load { dest: 4, literal_idx: 4 }" # 42
- "ObjectSet { obj: 0, key: 3, value: 4 }"
- "Load { dest: 5, literal_idx: 5 }" # "bool"
- "LoadTrue { dest: 6 }"
- "ObjectSet { obj: 0, key: 5, value: 6 }"
- "Load { dest: 7, literal_idx: 6 }" # "null"
- "LoadNull { dest: 8 }"
- "ObjectSet { obj: 0, key: 7, value: 8 }"
- "Load { dest: 9, literal_idx: 7 }" # "arr"
- "Load { dest: 10, literal_idx: 8 }" # [1, 2]
- "ObjectSet { obj: 0, key: 9, value: 10 }"
- "Return { value: 0 }"
want_result: {"str": "text", "num": 42, "bool": true, "null": null, "arr": [1, 2]}

View File

@@ -0,0 +1,165 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Predefined Global Bindings Test Suite
# Tests Rego's predefined data and input global bindings
# These bindings are always available in Rego policies
cases:
- note: load_data_basic
description: Test loading global data object
data:
users: ["alice", "bob"]
config:
debug: true
timeout: 30
input: null
literals: ["users"]
instructions:
- "LoadData { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }" # Load "users" literal
- "Index { dest: 2, container: 0, key: 1 }" # data.users
- "Return { value: 2 }"
want_result: ["alice", "bob"]
- note: load_input_basic
description: Test loading global input object
data: null
input:
request:
method: "GET"
path: "/api/users"
user:
id: 123
role: "admin"
literals: ["request", "method"]
instructions:
- "LoadInput { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }" # Load "request" literal
- "Index { dest: 2, container: 0, key: 1 }" # input.request
- "Load { dest: 3, literal_idx: 1 }" # Load "method" literal
- "Index { dest: 4, container: 2, key: 3 }" # input.request.method
- "Return { value: 4 }"
want_result: "GET"
- note: data_and_input_combined
description: Test using both data and input in same expression
data:
permissions:
admin: ["read", "write", "delete"]
user: ["read"]
input:
user:
role: "admin"
literals: ["permissions", "user", "role"]
instructions:
- "LoadData { dest: 0 }" # Load data
- "LoadInput { dest: 1 }" # Load input
- "Load { dest: 2, literal_idx: 1 }" # Load "user" literal
- "Index { dest: 3, container: 1, key: 2 }" # input.user
- "Load { dest: 4, literal_idx: 2 }" # Load "role" literal
- "Index { dest: 5, container: 3, key: 4 }" # input.user.role
- "Load { dest: 6, literal_idx: 0 }" # Load "permissions" literal
- "Index { dest: 7, container: 0, key: 6 }" # data.permissions
- "Index { dest: 8, container: 7, key: 5 }" # data.permissions[input.user.role]
- "Return { value: 8 }"
want_result: ["read", "write", "delete"]
- note: data_null_handling
description: Test behavior when data is null
data: null
input:
test: "value"
literals: []
instructions:
- "LoadData { dest: 0 }"
- "Return { value: 0 }"
want_result: null
- note: input_null_handling
description: Test behavior when input is null
data:
test: "value"
input: null
literals: []
instructions:
- "LoadInput { dest: 0 }"
- "Return { value: 0 }"
want_result: null
- note: nested_data_access
description: Test deep nested data access
data:
api:
v1:
endpoints:
users: "/api/v1/users"
posts: "/api/v1/posts"
input: null
literals: ["api", "v1", "endpoints", "users"]
instructions:
- "LoadData { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }" # "api"
- "Index { dest: 2, container: 0, key: 1 }" # data.api
- "Load { dest: 3, literal_idx: 1 }" # "v1"
- "Index { dest: 4, container: 2, key: 3 }" # data.api.v1
- "Load { dest: 5, literal_idx: 2 }" # "endpoints"
- "Index { dest: 6, container: 4, key: 5 }" # data.api.v1.endpoints
- "Load { dest: 7, literal_idx: 3 }" # "users"
- "Index { dest: 8, container: 6, key: 7 }" # data.api.v1.endpoints.users
- "Return { value: 8 }"
want_result: "/api/v1/users"
- note: array_access_with_input
description: Test array indexing with input values
data:
colors: ["red", "green", "blue"]
input:
selected_index: 1
literals: ["colors", "selected_index"]
instructions:
- "LoadData { dest: 0 }"
- "LoadInput { dest: 1 }"
- "Load { dest: 2, literal_idx: 0 }" # "colors"
- "Index { dest: 3, container: 0, key: 2 }" # data.colors
- "Load { dest: 4, literal_idx: 1 }" # "selected_index"
- "Index { dest: 5, container: 1, key: 4 }" # input.selected_index
- "Index { dest: 6, container: 3, key: 5 }" # data.colors[input.selected_index]
- "Return { value: 6 }"
want_result: "green"
- note: data_only_access
description: Test accessing data when input is not needed
data:
settings:
theme: "dark"
notifications: true
input: null
literals: ["settings", "theme"]
instructions:
- "LoadData { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }" # "settings"
- "Index { dest: 2, container: 0, key: 1 }" # data.settings
- "Load { dest: 3, literal_idx: 1 }" # "theme"
- "Index { dest: 4, container: 2, key: 3 }" # data.settings.theme
- "Return { value: 4 }"
want_result: "dark"
- note: input_only_access
description: Test accessing input when data is not needed
data: null
input:
request:
headers:
authorization: "Bearer token123"
literals: ["request", "headers", "authorization"]
instructions:
- "LoadInput { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }" # "request"
- "Index { dest: 2, container: 0, key: 1 }" # input.request
- "Load { dest: 3, literal_idx: 1 }" # "headers"
- "Index { dest: 4, container: 2, key: 3 }" # input.request.headers
- "Load { dest: 5, literal_idx: 2 }" # "authorization"
- "Index { dest: 6, container: 4, key: 5 }" # input.request.headers.authorization
- "Return { value: 6 }"
want_result: "Bearer token123"

View File

@@ -0,0 +1,217 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Resource Limits Test Suite
# Tests instruction count limits, recursion depth, and resource exhaustion scenarios
# Verifies VM handles resource constraints gracefully
cases:
- note: instruction_limit_in_simple_loop
description: Instruction limit exceeded in simple loop
example_rego: "some x in [1, 2, 3, 4, 5]; x > 0"
literals:
- 1
- 2
- 3
- 4
- 5
- 0
max_instructions: 10 # Limit low enough that execution exceeds it before completion
instruction_params:
loop_params:
- mode: "Any"
collection: 0
key_reg: 10
value_reg: 11
result_reg: 12
body_start: 13
loop_end: 17
instructions:
- "ArrayNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "ArrayPush { arr: 0, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "ArrayPush { arr: 0, value: 2 }"
- "Load { dest: 3, literal_idx: 2 }"
- "ArrayPush { arr: 0, value: 3 }"
- "Load { dest: 4, literal_idx: 3 }"
- "ArrayPush { arr: 0, value: 4 }"
- "Load { dest: 5, literal_idx: 4 }"
- "ArrayPush { arr: 0, value: 5 }"
- "LoopStart { params_index: 0 }"
- "Load { dest: 13, literal_idx: 5 }"
- "Gt { dest: 14, left: 11, right: 13 }"
- "AssertCondition { condition: 14 }"
- "LoopNext { body_start: 13, loop_end: 17 }"
- "Return { value: 12 }"
want_error: "exceeded maximum instruction limit"
- note: instruction_limit_in_comprehension
description: Instruction limit exceeded during comprehension
example_rego: "[x | x := [1, 2, 3, 4, 5][_]]"
literals:
- 1
- 2
- 3
- 4
- 5
max_instructions: 25
instruction_params:
comprehension_begin_params:
- mode: "Array"
collection_reg: 0
result_reg: 0
key_reg: 10
value_reg: 11
body_start: 13
comprehension_end: 17
loop_params:
- mode: "ForEach"
collection: 6
key_reg: 10
value_reg: 11
result_reg: 12
body_start: 13
loop_end: 17
instructions:
- "ArrayNew { dest: 6 }"
- "Load { dest: 1, literal_idx: 0 }"
- "ArrayPush { arr: 6, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "ArrayPush { arr: 6, value: 2 }"
- "Load { dest: 3, literal_idx: 2 }"
- "ArrayPush { arr: 6, value: 3 }"
- "Load { dest: 4, literal_idx: 3 }"
- "ArrayPush { arr: 6, value: 4 }"
- "Load { dest: 5, literal_idx: 4 }"
- "ArrayPush { arr: 6, value: 5 }"
- "ComprehensionBegin { params_index: 0 }"
- "LoopStart { params_index: 0 }"
- "ComprehensionYield { value_reg: 11 }"
- "LoopNext { body_start: 13, loop_end: 17 }"
- "ComprehensionEnd"
- "Return { value: 0 }"
want_error: "exceeded maximum instruction limit"
- note: instruction_limit_in_nested_loops
description: Instruction limit exceeded in nested loops
example_rego: "some x in [1, 2]; some y in [3, 4]; x + y > 0"
literals:
- 1
- 2
- 3
- 4
- 0
max_instructions: 30
instruction_params:
loop_params:
- mode: "Any"
collection: 0
key_reg: 10
value_reg: 11
result_reg: 12
body_start: 5
loop_end: 18
- mode: "Any"
collection: 2
key_reg: 20
value_reg: 21
result_reg: 22
body_start: 8
loop_end: 16
instructions:
- "ArrayNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "ArrayPush { arr: 0, value: 1 }"
- "Load { dest: 3, literal_idx: 1 }"
- "ArrayPush { arr: 0, value: 3 }"
- "LoopStart { params_index: 0 }"
- "ArrayNew { dest: 2 }"
- "Load { dest: 4, literal_idx: 2 }"
- "ArrayPush { arr: 2, value: 4 }"
- "Load { dest: 5, literal_idx: 3 }"
- "ArrayPush { arr: 2, value: 5 }"
- "LoopStart { params_index: 1 }"
- "Add { dest: 30, left: 11, right: 21 }"
- "Load { dest: 31, literal_idx: 4 }"
- "Gt { dest: 32, left: 30, right: 31 }"
- "AssertCondition { condition: 32 }"
- "LoopNext { body_start: 8, loop_end: 16 }"
- "AssertCondition { condition: 22 }"
- "LoopNext { body_start: 5, loop_end: 18 }"
- "Return { value: 12 }"
want_error: "exceeded maximum instruction limit"
- note: large_literal_table_access
description: Access literal near u16 bounds (valid case)
literals:
- 42
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Return { value: 0 }"
want_result: 42
- note: literal_index_out_of_bounds
description: Literal index beyond table bounds should error
literals:
- 42
instructions:
- "Load { dest: 0, literal_idx: 999 }"
- "Return { value: 0 }"
want_error: "Literal index 999 out of bounds"
- note: instruction_limit_with_early_return
description: Instruction limit allows early successful completion
example_rego: "some x in [1, 2]; x == 1"
literals:
- 1
- 2
max_instructions: 200 # Allow enough headroom for both execution modes
instruction_params:
loop_params:
- mode: "Any"
collection: 0
key_reg: 10
value_reg: 11
result_reg: 12
body_start: 6
loop_end: 9
instructions:
- "ArrayNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "ArrayPush { arr: 0, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "ArrayPush { arr: 0, value: 2 }"
- "LoopStart { params_index: 0 }"
- "Load { dest: 13, literal_idx: 0 }"
- "Eq { dest: 14, left: 11, right: 13 }"
- "AssertCondition { condition: 14 }"
- "LoopNext { body_start: 6, loop_end: 9 }"
- "Return { value: 12 }"
want_result: true
- note: many_registers_usage
description: Test using high register numbers near upper bound without error
example_rego: "Store values in registers near VM upper bound"
literals:
- 1
- 2
- 3
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 25, literal_idx: 1 }"
- "Load { dest: 49, literal_idx: 2 }"
- "Add { dest: 30, left: 0, right: 25 }"
- "Add { dest: 48, left: 30, right: 49 }"
- "Return { value: 48 }"
want_result: 6
- note: zero_instruction_limit
description: Zero instruction limit should error immediately
literals:
- 42
max_instructions: 0
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Return { value: 0 }"
want_error: "exceeded maximum instruction limit"

View File

@@ -0,0 +1,423 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Serialization Test Suite
# Tests round-trip serialization of compiled RVM programs
# Covers all instruction types, large programs, and edge cases
cases:
- note: serialization_basic_arithmetic
description: Serialize and deserialize simple arithmetic program
example_rego: "5 + 3"
literals:
- 5
- 3
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Add { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: 8
- note: serialization_comparisons
description: Serialize comparison instructions
example_rego: "10 > 5 && 3 < 7"
literals:
- 10
- 5
- 3
- 7
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Gt { dest: 2, left: 0, right: 1 }"
- "Load { dest: 3, literal_idx: 2 }"
- "Load { dest: 4, literal_idx: 3 }"
- "Lt { dest: 5, left: 3, right: 4 }"
- "And { dest: 6, left: 2, right: 5 }"
- "Return { value: 6 }"
want_result: true
- note: serialization_array_create
description: Serialize ArrayCreate with instruction_params
example_rego: "[1, 2, 3]"
literals:
- 1
- 2
- 3
instruction_params:
array_create_params:
- dest: 0
elements: [1, 2, 3]
instructions:
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Load { dest: 3, literal_idx: 2 }"
- "ArrayCreate { params_index: 0 }"
- "Return { value: 0 }"
want_result: [1, 2, 3]
- note: serialization_object_create
description: Serialize ObjectCreate with complex params
example_rego: "{\"a\": 1, \"b\": 2}"
literals:
- "a"
- 1
- "b"
- 2
- {}
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 4
literal_key_fields: []
fields:
- [1, 2]
- [3, 4]
instructions:
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Load { dest: 3, literal_idx: 2 }"
- "Load { dest: 4, literal_idx: 3 }"
- "ObjectCreate { params_index: 0 }"
- "Return { value: 0 }"
want_result: {"a": 1, "b": 2}
- note: serialization_set_operations
description: Serialize set creation and operations
example_rego: "{1, 2, 3}"
literals:
- 1
- 2
- 3
instructions:
- "SetNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "SetAdd { set: 0, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "SetAdd { set: 0, value: 2 }"
- "Load { dest: 3, literal_idx: 2 }"
- "SetAdd { set: 0, value: 3 }"
- "Return { value: 0 }"
want_result:
set!:
- 1
- 2
- 3
- note: serialization_loop_foreach
description: Serialize ForEach loop with params
example_rego: "[x | x = [1, 2, 3][_]]"
literals:
- 1
- 2
- 3
instruction_params:
array_create_params:
- dest: 0
elements: [1, 2, 3]
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 9
body_start: 6
loop_end: 8
instructions:
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Load { dest: 3, literal_idx: 2 }"
- "ArrayCreate { params_index: 0 }"
- "ArrayNew { dest: 6 }"
- "LoopStart { params_index: 0 }"
- "ArrayPush { arr: 6, value: 5 }"
- "LoopNext { body_start: 6, loop_end: 8 }"
- "Return { value: 6 }"
want_result: [1, 2, 3]
- note: serialization_loop_any
description: Serialize Any loop
example_rego: "some x in [1, 2, 3]; x > 2"
literals:
- 1
- 2
- 3
instruction_params:
array_create_params:
- dest: 0
elements: [1, 2, 3]
loop_params:
- mode: "Existential"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 6
body_start: 5
loop_end: 8
instructions:
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Load { dest: 3, literal_idx: 2 }"
- "ArrayCreate { params_index: 0 }"
- "LoopStart { params_index: 0 }"
- "Gt { dest: 8, left: 5, right: 2 }"
- "AssertCondition { condition: 8 }"
- "LoopNext { body_start: 5, loop_end: 8 }"
- "Return { value: 6 }"
want_result: true
- note: serialization_comprehension_array
description: Serialize array comprehension
example_rego: "[x * 2 | x = [1, 2, 3][_]]"
literals:
- 1
- 2
- 3
instruction_params:
array_create_params:
- dest: 0
elements: [1, 2, 3]
loop_params:
- mode: "ForEach"
collection: 0
key_reg: 4
value_reg: 5
result_reg: 9
body_start: 6
loop_end: 9
instructions:
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Load { dest: 3, literal_idx: 2 }"
- "ArrayCreate { params_index: 0 }"
- "ArrayNew { dest: 7 }"
- "LoopStart { params_index: 0 }"
- "Mul { dest: 6, left: 5, right: 2 }"
- "ArrayPush { arr: 7, value: 6 }"
- "LoopNext { body_start: 6, loop_end: 9 }"
- "Return { value: 7 }"
want_result: [2, 4, 6]
- note: serialization_indexed_access
description: Serialize indexing instructions
example_rego: "data.users[0].name"
literals:
- {"users": [{"name": "Alice"}, {"name": "Bob"}]}
- "users"
- 0
- "name"
instruction_params:
chained_index_params:
- dest: 4
root: 0
path_components:
- literal_idx: 1
- literal_idx: 2
- literal_idx: 3
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Load { dest: 2, literal_idx: 2 }"
- "Load { dest: 3, literal_idx: 3 }"
- "ChainedIndex { params_index: 0 }"
- "Return { value: 4 }"
want_result: "Alice"
- note: serialization_conditional_branching
description: Serialize conditional instructions
example_rego: "if true then 1 else 2"
literals:
- 1
- 2
instructions:
- "LoadTrue { dest: 0 }"
- "AssertCondition { condition: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Return { value: 1 }"
want_result: 1
- note: serialization_null_and_undefined
description: Serialize null and undefined handling
example_rego: "null"
literals: []
instructions:
- "LoadNull { dest: 0 }"
- "Return { value: 0 }"
want_result: null
- note: serialization_mixed_types
description: Serialize program with all value types
example_rego: "[1, \"text\", true, false, null, [2, 3], {\"k\": \"v\"}]"
literals:
- 1
- "text"
- 2
- 3
- "k"
- "v"
- {}
instruction_params:
array_create_params:
- dest: 6
elements: [3, 4]
- dest: 0
elements: [1, 2, 7, 8, 9, 6, 10]
object_create_params:
- dest: 10
template_literal_idx: 6
literal_key_fields: []
fields:
- [5, 11]
instructions:
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Load { dest: 3, literal_idx: 2 }"
- "Load { dest: 4, literal_idx: 3 }"
- "ArrayCreate { params_index: 0 }"
- "LoadTrue { dest: 7 }"
- "LoadFalse { dest: 8 }"
- "LoadNull { dest: 9 }"
- "Load { dest: 5, literal_idx: 4 }"
- "Load { dest: 11, literal_idx: 5 }"
- "ObjectCreate { params_index: 0 }"
- "ArrayCreate { params_index: 1 }"
- "Return { value: 0 }"
want_result: [1, "text", true, false, null, [2, 3], {"k": "v"}]
- note: serialization_large_program_50_instructions
description: Large program with many instructions (stress test)
example_rego: "Complex computation chain"
literals:
- 1
- 2
- 3
- 4
- 5
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Add { dest: 2, left: 0, right: 1 }"
- "Load { dest: 3, literal_idx: 2 }"
- "Mul { dest: 4, left: 2, right: 3 }"
- "Load { dest: 5, literal_idx: 3 }"
- "Sub { dest: 6, left: 4, right: 5 }"
- "Load { dest: 7, literal_idx: 4 }"
- "Div { dest: 8, left: 6, right: 7 }"
- "Load { dest: 9, literal_idx: 0 }"
- "Add { dest: 10, left: 8, right: 9 }"
- "Load { dest: 11, literal_idx: 1 }"
- "Mul { dest: 12, left: 10, right: 11 }"
- "Load { dest: 13, literal_idx: 2 }"
- "Add { dest: 14, left: 12, right: 13 }"
- "Load { dest: 15, literal_idx: 3 }"
- "Sub { dest: 16, left: 14, right: 15 }"
- "Load { dest: 17, literal_idx: 4 }"
- "Mul { dest: 18, left: 16, right: 17 }"
- "Load { dest: 19, literal_idx: 0 }"
- "Div { dest: 20, left: 18, right: 19 }"
- "Load { dest: 21, literal_idx: 1 }"
- "Add { dest: 22, left: 20, right: 21 }"
- "Load { dest: 23, literal_idx: 2 }"
- "Mul { dest: 24, left: 22, right: 23 }"
- "Load { dest: 25, literal_idx: 3 }"
- "Sub { dest: 26, left: 24, right: 25 }"
- "Load { dest: 27, literal_idx: 4 }"
- "Add { dest: 28, left: 26, right: 27 }"
- "Load { dest: 29, literal_idx: 0 }"
- "Mul { dest: 30, left: 28, right: 29 }"
- "Load { dest: 31, literal_idx: 1 }"
- "Div { dest: 32, left: 30, right: 31 }"
- "Load { dest: 33, literal_idx: 2 }"
- "Add { dest: 34, left: 32, right: 33 }"
- "Load { dest: 35, literal_idx: 3 }"
- "Sub { dest: 36, left: 34, right: 35 }"
- "Load { dest: 37, literal_idx: 4 }"
- "Mul { dest: 38, left: 36, right: 37 }"
- "Load { dest: 39, literal_idx: 0 }"
- "Add { dest: 40, left: 38, right: 39 }"
- "Load { dest: 41, literal_idx: 1 }"
- "Sub { dest: 42, left: 40, right: 41 }"
- "Load { dest: 43, literal_idx: 2 }"
- "Mul { dest: 44, left: 42, right: 43 }"
- "Load { dest: 45, literal_idx: 3 }"
- "Div { dest: 46, left: 44, right: 45 }"
- "Load { dest: 47, literal_idx: 4 }"
- "Add { dest: 48, left: 46, right: 47 }"
- "Return { value: 48 }"
want_result: 98
- note: serialization_large_literal_table
description: Program with many literal values
literals:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- "a"
- "b"
- "c"
- "d"
- "e"
- [1, 2, 3]
- {"key": "value"}
- true
- false
- null
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 5 }"
- "Add { dest: 2, left: 0, right: 1 }"
- "Load { dest: 3, literal_idx: 10 }"
- "Load { dest: 4, literal_idx: 15 }"
- "Return { value: 2 }"
want_result: 7
- note: serialization_nested_structures
description: Serialize deeply nested data structures
example_rego: "[{\"a\": [1, {\"b\": [2, 3]}]}]"
literals:
- 1
- 2
- 3
- "b"
- "a"
- {}
instruction_params:
array_create_params:
- dest: 3
elements: [2, 4]
- dest: 1
elements: [5, 6]
- dest: 0
elements: [7]
object_create_params:
- dest: 6
template_literal_idx: 5
literal_key_fields: []
fields:
- [8, 3]
- dest: 7
template_literal_idx: 5
literal_key_fields: []
fields:
- [9, 1]
instructions:
- "Load { dest: 5, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Load { dest: 4, literal_idx: 2 }"
- "ArrayCreate { params_index: 0 }"
- "Load { dest: 8, literal_idx: 3 }"
- "ObjectCreate { params_index: 0 }"
- "ArrayCreate { params_index: 1 }"
- "Load { dest: 9, literal_idx: 4 }"
- "ObjectCreate { params_index: 1 }"
- "ArrayCreate { params_index: 2 }"
- "Return { value: 0 }"
want_result: [{"a": [1, {"b": [2, 3]}]}]

View File

@@ -0,0 +1,284 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Set Operations Test Suite
# Tests set creation, deduplication, and membership testing
# Covers complex value deduplication, nested sets, and undefined handling
cases:
- note: set_deduplication_simple
description: Set automatically deduplicates simple values
example_rego: "{1, 2, 1, 3, 2}"
literals:
- 1
- 2
- 3
instructions:
- "SetNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }" # 1
- "SetAdd { set: 0, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }" # 2
- "SetAdd { set: 0, value: 2 }"
- "SetAdd { set: 0, value: 1 }" # 1 again (duplicate)
- "Load { dest: 3, literal_idx: 2 }" # 3
- "SetAdd { set: 0, value: 3 }"
- "SetAdd { set: 0, value: 2 }" # 2 again (duplicate)
- "Return { value: 0 }"
want_result:
set!:
- 1
- 2
- 3
- note: set_deduplication_objects
description: Set deduplicates identical objects
example_rego: "{{\"a\": 1}, {\"b\": 2}, {\"a\": 1}}"
literals:
- {"a": 1}
- {"b": 2}
instructions:
- "SetNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }" # {"a": 1}
- "SetAdd { set: 0, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }" # {"b": 2}
- "SetAdd { set: 0, value: 2 }"
- "SetAdd { set: 0, value: 1 }" # {"a": 1} again (duplicate)
- "Return { value: 0 }"
want_result:
set!:
- {"a": 1}
- {"b": 2}
- note: set_deduplication_arrays
description: Set deduplicates identical arrays
example_rego: "{[1, 2], [3, 4], [1, 2]}"
literals:
- [1, 2]
- [3, 4]
instructions:
- "SetNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }" # [1, 2]
- "SetAdd { set: 0, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }" # [3, 4]
- "SetAdd { set: 0, value: 2 }"
- "SetAdd { set: 0, value: 1 }" # [1, 2] again (duplicate)
- "Return { value: 0 }"
want_result:
set!:
- [1, 2]
- [3, 4]
- note: set_with_mixed_types
description: Set can contain different types
example_rego: "{1, \"text\", true, null, [1, 2], {\"a\": 1}}"
literals:
- 1
- "text"
- [1, 2]
- {"a": 1}
instructions:
- "SetNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }" # 1
- "SetAdd { set: 0, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }" # "text"
- "SetAdd { set: 0, value: 2 }"
- "LoadTrue { dest: 3 }"
- "SetAdd { set: 0, value: 3 }"
- "LoadNull { dest: 4 }"
- "SetAdd { set: 0, value: 4 }"
- "Load { dest: 5, literal_idx: 2 }" # [1, 2]
- "SetAdd { set: 0, value: 5 }"
- "Load { dest: 6, literal_idx: 3 }" # {"a": 1}
- "SetAdd { set: 0, value: 6 }"
- "Return { value: 0 }"
want_result:
set!:
- 1
- "text"
- true
- null
- [1, 2]
- {"a": 1}
- note: set_contains_simple
description: Contains check on set with simple values
example_rego: "2 in {1, 2, 3}"
literals:
- 1
- 2
- 3
instructions:
- "SetNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "SetAdd { set: 0, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "SetAdd { set: 0, value: 2 }"
- "Load { dest: 3, literal_idx: 2 }"
- "SetAdd { set: 0, value: 3 }"
- "Contains { dest: 4, collection: 0, value: 2 }"
- "Return { value: 4 }"
want_result: true
- note: set_contains_object
description: Contains check with object value
example_rego: "{\"a\": 1} in {{\"a\": 1}, {\"b\": 2}}"
literals:
- {"a": 1}
- {"b": 2}
instructions:
- "SetNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "SetAdd { set: 0, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "SetAdd { set: 0, value: 2 }"
- "Contains { dest: 3, collection: 0, value: 1 }"
- "Return { value: 3 }"
want_result: true
- note: set_contains_not_found
description: Contains returns false when value not in set
example_rego: "5 in {1, 2, 3}"
literals:
- 1
- 2
- 3
- 5
instructions:
- "SetNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "SetAdd { set: 0, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "SetAdd { set: 0, value: 2 }"
- "Load { dest: 3, literal_idx: 2 }"
- "SetAdd { set: 0, value: 3 }"
- "Load { dest: 4, literal_idx: 3 }"
- "Contains { dest: 5, collection: 0, value: 4 }"
- "Return { value: 5 }"
want_result: false
- note: set_create_from_registers
description: SetCreate instruction creates set from multiple registers
literals:
- 1
- 2
- 3
instruction_params:
set_create_params:
- dest: 0
elements: [1, 2, 3, 1] # Duplicate 1
instructions:
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Load { dest: 3, literal_idx: 2 }"
- "SetCreate { params_index: 0 }"
- "Return { value: 0 }"
want_result:
set!:
- 1
- 2
- 3
- note: set_create_with_undefined_element
description: SetCreate with undefined element returns undefined (short-circuit)
literals:
- 1
- 2
- "#undefined"
instruction_params:
set_create_params:
- dest: 0
elements: [1, 2, 3] # r3 is undefined
instructions:
- "Load { dest: 1, literal_idx: 0 }"
- "Load { dest: 2, literal_idx: 1 }"
- "Load { dest: 3, literal_idx: 2 }" # r3 is explicitly undefined
- "SetCreate { params_index: 0 }"
- "Return { value: 0 }"
want_result: "#undefined"
- note: set_large_collection
description: Set with many elements (stress test)
literals:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
instructions:
- "SetNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "SetAdd { set: 0, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "SetAdd { set: 0, value: 2 }"
- "Load { dest: 3, literal_idx: 2 }"
- "SetAdd { set: 0, value: 3 }"
- "Load { dest: 4, literal_idx: 3 }"
- "SetAdd { set: 0, value: 4 }"
- "Load { dest: 5, literal_idx: 4 }"
- "SetAdd { set: 0, value: 5 }"
- "Load { dest: 6, literal_idx: 5 }"
- "SetAdd { set: 0, value: 6 }"
- "Load { dest: 7, literal_idx: 6 }"
- "SetAdd { set: 0, value: 7 }"
- "Load { dest: 8, literal_idx: 7 }"
- "SetAdd { set: 0, value: 8 }"
- "Load { dest: 9, literal_idx: 8 }"
- "SetAdd { set: 0, value: 9 }"
- "Load { dest: 10, literal_idx: 9 }"
- "SetAdd { set: 0, value: 10 }"
- "Return { value: 0 }"
want_result:
set!:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- note: set_empty
description: Empty set creation
example_rego: "set()"
literals: []
instructions:
- "SetNew { dest: 0 }"
- "Return { value: 0 }"
want_result:
set!: []
- note: set_nested_in_set
description: Set containing other sets
example_rego: "{{1, 2}, {3, 4}}"
literals:
- set!:
- 1
- 2
- set!:
- 3
- 4
instructions:
- "SetNew { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "SetAdd { set: 0, value: 1 }"
- "Load { dest: 2, literal_idx: 1 }"
- "SetAdd { set: 0, value: 2 }"
- "Return { value: 0 }"
want_result:
set!:
-
set!:
- 1
- 2
-
set!:
- 3
- 4

View File

@@ -0,0 +1,224 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Type Errors Test Suite
# Tests graceful error handling for type mismatches across instruction families
# Verifies that VM returns appropriate errors instead of panicking
cases:
# Mixed-type arithmetic operations
- note: arithmetic_add_string_to_int
description: Adding string to int should error gracefully
example_rego: "1 + \"text\""
literals:
- 1
- "text"
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Add { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_error: "Cannot add"
- note: arithmetic_mul_bool_by_int
description: Multiplying boolean by int should error
example_rego: "true * 5"
literals:
- 5
instructions:
- "LoadTrue { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Mul { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_error: "Cannot multiply"
- note: arithmetic_div_null_by_int
description: Dividing null by int should error
example_rego: "null / 2"
literals:
- 2
instructions:
- "LoadNull { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Div { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_error: "Cannot divide"
- note: arithmetic_sub_array_from_int
description: Subtracting array from int should error
example_rego: "10 - [1, 2]"
literals:
- 10
- [1, 2]
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Sub { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_error: "Cannot subtract"
- note: arithmetic_add_object_to_int
description: Adding object to int should error
example_rego: "5 + {\"a\": 1}"
literals:
- 5
- {"a": 1}
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Add { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_error: "Cannot add"
# Logical operations on non-booleans
- note: logical_and_string_with_bool
description: AND with string operand should error
example_rego: "\"text\" && true"
literals:
- "text"
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "LoadTrue { dest: 1 }"
- "And { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_error: "#undefined"
- note: logical_or_int_with_bool
description: OR with int operand should error
example_rego: "42 || false"
literals:
- 42
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "LoadFalse { dest: 1 }"
- "Or { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_error: "#undefined"
- note: logical_not_int
description: NOT with int operand should error
example_rego: "!42"
literals:
- 42
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Not { dest: 1, operand: 0 }"
- "Return { value: 1 }"
want_error: "#undefined"
# Invalid indexing operations
- note: index_int_with_string_key
description: Indexing int with string should return undefined
example_rego: "123[\"key\"]"
literals:
- 123
- "key"
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "Return { value: 2 }"
want_result: "#undefined"
- note: index_null_with_int
description: Indexing null should return undefined
example_rego: "null[0]"
literals:
- 0
instructions:
- "LoadNull { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "Return { value: 2 }"
want_result: "#undefined"
- note: index_bool_with_string
description: Indexing boolean should return undefined
example_rego: "true.field"
literals:
- "field"
instructions:
- "LoadTrue { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "Return { value: 2 }"
want_result: "#undefined"
- note: index_string_with_object_key
description: Indexing string with object key should return undefined
example_rego: "\"text\"[{}]"
literals:
- "text"
- {}
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Index { dest: 2, container: 0, key: 1 }"
- "Return { value: 2 }"
want_result: "#undefined"
# Contains with incompatible types
- note: contains_in_int
description: Contains check on int should return false
example_rego: "1 in 123"
literals:
- 123
- 1
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Contains { dest: 2, collection: 0, value: 1 }"
- "Return { value: 2 }"
want_result: false
- note: contains_in_bool
description: Contains check on boolean should return false
example_rego: "1 in true"
literals:
- 1
instructions:
- "LoadTrue { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Contains { dest: 2, collection: 0, value: 1 }"
- "Return { value: 2 }"
want_result: false
- note: contains_in_null
description: Contains check on null should return false
example_rego: "1 in null"
literals:
- 1
instructions:
- "LoadNull { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Contains { dest: 2, collection: 0, value: 1 }"
- "Return { value: 2 }"
want_result: false
# Comparison operations with incompatible types
- note: compare_string_lt_int
description: Comparing string < int returns ordering result in non-strict; errors in strict
example_rego: "\"abc\" < 5"
literals:
- "abc"
- 5
instructions:
- "Load { dest: 0, literal_idx: 0 }"
- "Load { dest: 1, literal_idx: 1 }"
- "Lt { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: false
want_error_strict: "#undefined"
- note: compare_bool_gt_array
description: Comparing bool > array returns ordering result in non-strict; errors in strict
example_rego: "true > [1, 2]"
literals:
- [1, 2]
instructions:
- "LoadTrue { dest: 0 }"
- "Load { dest: 1, literal_idx: 0 }"
- "Gt { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: false
want_error_strict: "#undefined"

View File

@@ -0,0 +1,274 @@
# Virtual Data Lookup Test Suite
# Exercises VirtualDataDocumentLookup across data/rule blending and errors.
cases:
- note: virtual_lookup_base_data_only
description: Lookup resolves to base data when no rules override
data:
services:
api:
enabled: true
literals:
- "services"
- "api"
- "enabled"
instruction_params:
virtual_data_document_lookup_params:
- dest: 3
path_components:
- literal_idx: 0
- literal_idx: 1
- literal_idx: 2
instructions:
- "VirtualDataDocumentLookup { params_index: 0 }"
- "Return { value: 3 }"
want_result: true
- note: virtual_lookup_rule_override
description: Lookup combines rule result overriding base data
data:
services:
api:
enabled: false
literals:
- "services"
- "api"
- "enabled"
rule_infos:
- rule_type: Complete
definitions:
- [2]
rule_tree:
data:
services:
api:
enabled: 0
instruction_params:
virtual_data_document_lookup_params:
- dest: 3
path_components:
- literal_idx: 0
- literal_idx: 1
- literal_idx: 2
instructions:
- "VirtualDataDocumentLookup { params_index: 0 }"
- "Return { value: 3 }"
- "RuleInit { result_reg: 1, rule_index: 0 }"
- "LoadTrue { dest: 1 }"
- "RuleReturn {}"
want_result: true
- note: virtual_lookup_rule_subobject
description: Lookup merges nested rule tree subobjects
data:
tenants:
alpha:
feature: "beta"
literals:
- "tenants"
- "alpha"
- "feature"
rule_infos:
- rule_type: Complete
definitions:
- [2]
rule_tree:
data:
tenants:
alpha:
extra: 0
instruction_params:
virtual_data_document_lookup_params:
- dest: 4
path_components:
- literal_idx: 0
- literal_idx: 1
instructions:
- "VirtualDataDocumentLookup { params_index: 0 }"
- "Return { value: 4 }"
- "RuleInit { result_reg: 1, rule_index: 0 }"
- "Load { dest: 1, literal_idx: 2 }"
- "RuleReturn {}"
want_result:
feature: "beta"
extra: "feature"
- note: virtual_lookup_invalid_rule_index
description: Invalid rule index produces corresponding error
literals:
- "services"
instruction_params:
virtual_data_document_lookup_params:
- dest: 1
path_components:
- literal_idx: 0
rule_tree:
data:
services: 99
instructions:
- "VirtualDataDocumentLookup { params_index: 0 }"
want_error: "Rule index 99 out of bounds"
- note: virtual_lookup_rule_data_conflict
description: Rule completely replaces data at the same path
data:
config:
mode: "data_value"
literals:
- "config"
- "mode"
- "rule_value"
rule_infos:
- rule_type: Complete
definitions:
- [3]
rule_tree:
data:
config:
mode: 0
instruction_params:
virtual_data_document_lookup_params:
- dest: 3
path_components:
- literal_idx: 0
- literal_idx: 1
instructions:
- "VirtualDataDocumentLookup { params_index: 0 }"
- "Return { value: 3 }"
- "RuleInit { result_reg: 1, rule_index: 0 }"
- "Load { dest: 1, literal_idx: 2 }"
- "RuleReturn {}"
want_result: "rule_value"
- note: virtual_lookup_deep_path_5_levels
description: Lookup with 5-level deep path
data:
level1:
level2:
level3:
level4:
level5: "deep_value"
literals:
- "level1"
- "level2"
- "level3"
- "level4"
- "level5"
instruction_params:
virtual_data_document_lookup_params:
- dest: 5
path_components:
- literal_idx: 0
- literal_idx: 1
- literal_idx: 2
- literal_idx: 3
- literal_idx: 4
instructions:
- "VirtualDataDocumentLookup { params_index: 0 }"
- "Return { value: 5 }"
want_result: "deep_value"
- note: virtual_lookup_deep_path_with_array_indices
description: Lookup path with array index components
data:
containers:
- name: "first"
- name: "second"
- name: "third"
literals:
- "containers"
- 1
- "name"
instruction_params:
virtual_data_document_lookup_params:
- dest: 3
path_components:
- literal_idx: 0
- literal_idx: 1
- literal_idx: 2
instructions:
- "VirtualDataDocumentLookup { params_index: 0 }"
- "Return { value: 3 }"
want_result: "second"
- note: virtual_lookup_mixed_data_rules_deep
description: Deep lookup with rules at multiple levels
data:
org:
dept:
team:
lead: "data_lead"
literals:
- "org"
- "dept"
- "team"
- "lead"
- "rule_lead"
rule_infos:
- rule_type: Complete
definitions:
- [3]
rule_tree:
data:
org:
dept:
team:
lead: 0
instruction_params:
virtual_data_document_lookup_params:
- dest: 5
path_components:
- literal_idx: 0
- literal_idx: 1
- literal_idx: 2
- literal_idx: 3
instructions:
- "VirtualDataDocumentLookup { params_index: 0 }"
- "Return { value: 5 }"
- "RuleInit { result_reg: 1, rule_index: 0 }"
- "Load { dest: 1, literal_idx: 4 }"
- "RuleReturn {}"
want_result: "rule_lead"
- note: virtual_lookup_nonexistent_deep_path
description: Lookup of non-existent deep path returns undefined
data:
root:
child: "value"
literals:
- "root"
- "nonexistent"
- "path"
instruction_params:
virtual_data_document_lookup_params:
- dest: 3
path_components:
- literal_idx: 0
- literal_idx: 1
- literal_idx: 2
instructions:
- "VirtualDataDocumentLookup { params_index: 0 }"
- "Return { value: 3 }"
want_result: "#undefined"
- note: virtual_lookup_partial_match_deep_path
description: Partial path match returns undefined (not intermediate object)
data:
a:
b:
c: "value"
literals:
- "a"
- "b"
- "d"
instruction_params:
virtual_data_document_lookup_params:
- dest: 3
path_components:
- literal_idx: 0
- literal_idx: 1
- literal_idx: 2
instructions:
- "VirtualDataDocumentLookup { params_index: 0 }"
- "Return { value: 3 }"
want_result: "#undefined"