Skip to content

Commit

Permalink
{Python,Ruby,JS}: Add class_id information to ExternalInstance valu…
Browse files Browse the repository at this point in the history
…es; use for in-VM IsA checks (#1472)

Create `Constants` type which wraps two HashMaps describing Symbol ->
Term and ID -> Symbol relationships. This second ID -> Symbol index is
populated by reading the optional `class_id` information in
`ExternalInstance` and will be used to implement in-core IsA checks
where present.

Extend `vm#isa` to attempt an in-core check when the `ExternalInstance` value
we're processing has the necessary `class_id` information declared. At time of
commit Python is the only language which is capabale of passing this information
to the core.

* {JS,Python,Ruby} Format ExternalInstance values with user-provided class names if present  (#1488)

Co-authored-by: Gabe Jackson <17556281+gj@users.noreply.github.com>
  • Loading branch information
patrickod and gj authored Jan 24, 2022
1 parent 59e572a commit 2631806
Show file tree
Hide file tree
Showing 21 changed files with 280 additions and 25 deletions.
22 changes: 20 additions & 2 deletions languages/js/src/Host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,12 +456,29 @@ export class Host<Query, Resource> {
}
default: {
let instanceId: number | undefined = undefined;
if (isConstructor(v)) instanceId = this.getType(v)?.id;
let classId: number | undefined = undefined;

// pass a string class repr *for registered types only*, otherwise pass
// undefined (allow core to differentiate registered or not)
const v_cast = v as NullishOrHasConstructor;
let classRepr: string | undefined = v_cast?.constructor?.name;
let classRepr: string | undefined = undefined;

if (isConstructor(v)) {
instanceId = this.getType(v)?.id;
classId = instanceId;
classRepr = this.getType(v)?.name;
} else {
const v_constructor: Class | undefined = v_cast?.constructor;

// pass classId for instances of *registered classes* only
if (v_constructor !== undefined && this.types.has(v_constructor)) {
classId = this.getType(v_constructor)?.id;
classRepr = this.getType(v_constructor)?.name;
}
}

// pass classRepr for *registered* classes only, pass undefined
// otherwise
if (classRepr !== undefined && !this.types.has(classRepr)) {
classRepr = undefined;
}
Expand All @@ -474,6 +491,7 @@ export class Host<Query, Resource> {
constructor: undefined,
repr: repr(v),
class_repr: classRepr,
class_id: classId,
},
},
};
Expand Down
2 changes: 1 addition & 1 deletion languages/js/src/Polar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@ export class Polar<Query, Resource> {

// Register MROs, load Polar code, and check inline queries.
private async loadSources(sources: Source[]): Promise<void> {
this.getHost().registerMros();
this.#ffiPolar.load(sources);
this.processMessages();
return this.checkInlineQueries();
Expand Down Expand Up @@ -244,6 +243,7 @@ export class Polar<Query, Resource> {
registerClass(cls: Class, params?: ClassParams): void {
const clsName = this.getHost().cacheClass(cls, params);
this.registerConstant(cls, clsName);
this.getHost().registerMros();
}

/**
Expand Down
2 changes: 2 additions & 0 deletions languages/js/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ interface PolarInstance {
instance_id: number;
repr: string;
class_repr?: string;
class_id?: number;
constructor?: PolarTerm;
};
}
Expand Down Expand Up @@ -691,6 +692,7 @@ export interface ClassParams {
* `name` property.
*/
name?: string;

/**
* A Map or object with string keys containing types for fields. Used for
* data filtering.
Expand Down
15 changes: 11 additions & 4 deletions languages/python/oso/polar/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,23 +294,30 @@ def to_polar(self, v):
# ]
# }
else:
instance_id = None
import inspect

instance_id = None
class_id = None

# maintain consistent IDs for registered classes
if inspect.isclass(v):
if v in self.types:
instance_id = self.types[v].id
class_id = instance_id = self.types[v].id

# pass the class_repr only for registered types otherwise None
class_repr = type(v).__name__
class_repr = class_repr if class_repr in self.types else None
class_repr = self.types[type(v)].name if type(v) in self.types else None

# pass class_id for classes & instances of registered classes,
# otherwise pass None
if type(v) in self.types:
class_id = self.types[type(v)].id

val = {
"ExternalInstance": {
"instance_id": self.cache_instance(v, instance_id),
"repr": None,
"class_repr": class_repr,
"class_id": class_id,
}
}
term = {"value": val}
Expand Down
7 changes: 4 additions & 3 deletions languages/python/oso/polar/polar.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ def load_str(self, string: str):

# Register MROs, load Polar code, and check inline queries.
def _load_sources(self, sources: List[Source]):
self.host.register_mros()
self.ffi_polar.load(sources)
self.check_inline_queries()

Expand Down Expand Up @@ -241,12 +240,14 @@ def register_class(
data filtering.
"""
# TODO: let's add example usage here or at least a proper docstring for the arguments
cls_name = self.host.cache_class(

name = self.host.cache_class(
cls,
name=name,
fields=fields,
)
self.register_constant(cls, cls_name)
self.register_constant(cls, name)
self.host.register_mros()

def register_constant(self, value, name):
"""
Expand Down
17 changes: 12 additions & 5 deletions languages/ruby/lib/oso/polar/host.rb
Original file line number Diff line number Diff line change
Expand Up @@ -319,17 +319,24 @@ def to_polar(value) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplex
end
else
instance_id = nil
instance_id = types[value].id if value.is_a?(Class) && types.key?(value)

# only pass class_repr for registered types
class_id = nil
class_repr = nil
class_repr = value.class.to_s if types.key?(value.class)
# id=class_id,

# pass `class_id` & `class_repr` for registered types
if value.is_a?(Class) && types.key?(value)
instance_id = class_id = types[value].id
elsif types.key?(value.class)
class_id = types[value.class].id
class_repr = types[value.class].name
end

{
'ExternalInstance' => {
'instance_id' => cache_instance(value, id: instance_id),
'repr' => nil,
'class_repr' => class_repr
'class_repr' => class_repr,
'class_id' => class_id
}
}
end
Expand Down
2 changes: 1 addition & 1 deletion languages/ruby/lib/oso/polar/polar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ def register_class(cls, name: nil, fields: nil, combine_query: nil, build_query:
exec_query: exec_query || maybe_mtd(cls, :exec_query)
)
register_constant(cls, name: name)
host.register_mros
end

# Register a Ruby object with Polar.
Expand Down Expand Up @@ -336,7 +337,6 @@ def maybe_mtd(cls, mtd)
# Register MROs, load Polar code, and check inline queries.
# @param sources [Array<Source>] Polar sources to load.
def load_sources(sources)
host.register_mros
ffi_polar.load(sources)
check_inline_queries
end
Expand Down
1 change: 1 addition & 0 deletions languages/rust/oso/src/host/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ This may mean you performed an operation in your policy over an unbound variable
instance_id: id,
repr: Some(std::any::type_name::<Self>().to_owned()),
class_repr: Some(std::any::type_name::<Self>().to_owned()),
class_id: None,
})
}
PolarValue::List(l) => {
Expand Down
40 changes: 40 additions & 0 deletions polar-core/src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use crate::terms::{Symbol, Term};
use std::collections::HashMap;

#[derive(Default, Debug)]
pub(crate) struct Constants {
// Symbol -> Term (populated by *all* constants)
pub symbol_to_term: HashMap<Symbol, Term>,
// Symbol -> class_id (populated by class constants)
class_symbol_to_id: HashMap<Symbol, u64>,
// class_id -> Symbol (populated by class constants)
class_id_to_symbol: HashMap<u64, Symbol>,
}

impl Constants {
pub(crate) fn insert(&mut self, name: Symbol, value: Term) {
self.symbol_to_term.insert(name, value);
}

pub(crate) fn insert_class(&mut self, name: Symbol, value: Term, class_id: u64) {
self.insert(name.clone(), value);
self.class_symbol_to_id.insert(name.clone(), class_id);
self.class_id_to_symbol.insert(class_id, name);
}

pub(crate) fn contains_key(&self, name: &Symbol) -> bool {
self.symbol_to_term.contains_key(name)
}

pub(crate) fn get(&self, name: &Symbol) -> Option<&Term> {
self.symbol_to_term.get(name)
}

pub(crate) fn get_class_id_for_symbol(&self, symbol: &Symbol) -> Option<&u64> {
self.class_symbol_to_id.get(symbol)
}

pub(crate) fn get_symbol_for_class_id(&self, id: &u64) -> Option<&Symbol> {
self.class_id_to_symbol.get(id)
}
}
3 changes: 3 additions & 0 deletions polar-core/src/folder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ pub fn fold_external_instance<T: Folder>(
constructor,
repr,
class_repr,
class_id,
}: ExternalInstance,
fld: &mut T,
) -> ExternalInstance {
Expand All @@ -155,6 +156,7 @@ pub fn fold_external_instance<T: Folder>(
constructor: constructor.map(|t| fld.fold_term(t)),
repr: repr.map(|r| fld.fold_string(r)),
class_repr: class_repr.map(|r| fld.fold_string(r)),
class_id: class_id.map(|id| fld.fold_instance_id(id)),
}
}

Expand Down Expand Up @@ -271,6 +273,7 @@ mod tests {
constructor: None,
repr: None,
class_repr: None,
class_id: None,
}));
let instance_pattern = term!(value!(Pattern::Instance(InstanceLiteral {
tag: sym!("d"),
Expand Down
44 changes: 40 additions & 4 deletions polar-core/src/kb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet};
use std::sync::Arc;

pub use super::bindings::Bindings;
use super::constants::Constants;
use super::counter::Counter;
use super::diagnostic::Diagnostic;
use super::error::{invalid_state, PolarError, PolarResult, RuntimeError, ValidationError};
Expand All @@ -26,9 +27,9 @@ impl RuleParamMatch {
pub struct KnowledgeBase {
/// A map of bindings: variable name → value. The VM uses a stack internally,
/// but can translate to and from this type.
constants: Bindings,
constants: Constants,
/// Map of class name -> MRO list where the MRO list is a list of class instance IDs
mro: HashMap<Symbol, Vec<u64>>,
pub mro: HashMap<Symbol, Vec<u64>>,

/// Map from contents to filename for files loaded into the KB.
loaded_content: HashMap<String, String>,
Expand Down Expand Up @@ -574,7 +575,26 @@ impl KnowledgeBase {
}
.into());
}
self.constants.insert(name, value);

if let Value::ExternalInstance(ExternalInstance {
class_id,
instance_id,
..
}) = *value.value()
{
if class_id.map_or(false, |id| id == instance_id) {
// ExternalInstance values with matching class_id & instance_id represent *classes*
// whose class_id we want to index for later type checking & MRO resolution
self.constants.insert_class(name, value, instance_id)
} else {
// ExternalInstance values with differing `class_id` and
// `instance_id` represent *instances* of classes whose class_id
// should not be registered
self.constants.insert(name, value)
}
} else {
self.constants.insert(name, value)
}
Ok(())
}

Expand All @@ -585,7 +605,15 @@ impl KnowledgeBase {

/// Getter for `constants` map without exposing it for mutation.
pub fn get_registered_constants(&self) -> &Bindings {
&self.constants
&self.constants.symbol_to_term
}

pub(crate) fn get_symbol_for_class_id(&self, id: &u64) -> Option<&Symbol> {
self.constants.get_symbol_for_class_id(id)
}

pub(crate) fn get_class_id_for_symbol(&self, symbol: &Symbol) -> Option<&u64> {
self.constants.get_class_id_for_symbol(symbol)
}

// TODO(gj): currently no way to distinguish classes from other registered constants in the
Expand Down Expand Up @@ -879,6 +907,7 @@ mod tests {
constructor: None,
repr: None,
class_repr: None,
class_id: None,
})),
)
.unwrap();
Expand Down Expand Up @@ -1321,6 +1350,7 @@ mod tests {
constructor: None,
repr: None,
class_repr: None,
class_id: None,
})),
)
.unwrap();
Expand All @@ -1331,6 +1361,7 @@ mod tests {
constructor: None,
repr: None,
class_repr: None,
class_id: None,
})),
)
.unwrap();
Expand All @@ -1341,6 +1372,7 @@ mod tests {
constructor: None,
repr: None,
class_repr: None,
class_id: None,
})),
)
.unwrap();
Expand Down Expand Up @@ -1401,6 +1433,7 @@ mod tests {
constructor: None,
repr: None,
class_repr: None,
class_id: None,
})),
)
.unwrap();
Expand All @@ -1411,6 +1444,7 @@ mod tests {
constructor: None,
repr: None,
class_repr: None,
class_id: None,
})),
)
.unwrap();
Expand All @@ -1421,6 +1455,7 @@ mod tests {
constructor: None,
repr: None,
class_repr: None,
class_id: None,
})),
)
.unwrap();
Expand All @@ -1432,6 +1467,7 @@ mod tests {
constructor: None,
repr: None,
class_repr: None,
class_id: None,
})),
)
.unwrap();
Expand Down
1 change: 1 addition & 0 deletions polar-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ extern crate maplit;
pub mod macros;

mod bindings;
mod constants;
mod counter;
pub mod data_filtering;
mod debugger;
Expand Down
1 change: 1 addition & 0 deletions polar-core/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ impl From<u64> for ExternalInstance {
constructor: None,
repr: None,
class_repr: None,
class_id: None,
}
}
}
Expand Down
Loading

1 comment on commit 2631806

@github-actions
Copy link

Choose a reason for hiding this comment

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

Rust Benchmark

Benchmark suite Current: 2631806 Previous: 59e572a Ratio
rust_get_attribute 45959 ns/iter (± 1938) 46361 ns/iter (± 2641) 0.99
n_plus_one/100 2265843 ns/iter (± 5634) 2257978 ns/iter (± 34289) 1.00
n_plus_one/500 10861165 ns/iter (± 23233) 10905597 ns/iter (± 72665) 1.00
n_plus_one/1000 21548110 ns/iter (± 105892) 21830629 ns/iter (± 358299) 0.99
unify_once 1029 ns/iter (± 51) 1009 ns/iter (± 40) 1.02
unify_twice 2701 ns/iter (± 86) 2635 ns/iter (± 77) 1.03
many_rules 63917 ns/iter (± 1265) 65281 ns/iter (± 1647) 0.98
fib/5 536931 ns/iter (± 7263) 547190 ns/iter (± 9060) 0.98
prime/3 17987 ns/iter (± 698) 18347 ns/iter (± 1101) 0.98
prime/23 18013 ns/iter (± 699) 18427 ns/iter (± 930) 0.98
prime/43 17972 ns/iter (± 694) 18394 ns/iter (± 957) 0.98
prime/83 17953 ns/iter (± 704) 18365 ns/iter (± 1049) 0.98
prime/255 16390 ns/iter (± 598) 16801 ns/iter (± 834) 0.98
indexed/100 5981 ns/iter (± 657) 6481 ns/iter (± 839) 0.92
indexed/500 7450 ns/iter (± 1848) 8729 ns/iter (± 2935) 0.85
indexed/1000 9610 ns/iter (± 256) 10191 ns/iter (± 893) 0.94
indexed/10000 22950 ns/iter (± 1748) 42575 ns/iter (± 2176) 0.54
not 6005 ns/iter (± 92) 6252 ns/iter (± 152) 0.96
double_not 12418 ns/iter (± 223) 13117 ns/iter (± 382) 0.95
De_Morgan_not 8073 ns/iter (± 176) 8337 ns/iter (± 279) 0.97
load_policy 888231 ns/iter (± 7816) 888491 ns/iter (± 1213) 1.00
partial_and/1 32458 ns/iter (± 1389) 32803 ns/iter (± 1701) 0.99
partial_and/5 113933 ns/iter (± 2943) 111508 ns/iter (± 3408) 1.02
partial_and/10 216074 ns/iter (± 4378) 212796 ns/iter (± 5548) 1.02
partial_and/20 439151 ns/iter (± 5075) 431291 ns/iter (± 8586) 1.02
partial_and/40 932709 ns/iter (± 10636) 936419 ns/iter (± 12533) 1.00
partial_and/80 2098932 ns/iter (± 31396) 2090541 ns/iter (± 7359) 1.00
partial_and/100 2780242 ns/iter (± 5896) 2753202 ns/iter (± 10271) 1.01
partial_rule_depth/1 104837 ns/iter (± 3932) 105865 ns/iter (± 4185) 0.99
partial_rule_depth/5 338105 ns/iter (± 5372) 342655 ns/iter (± 7739) 0.99
partial_rule_depth/10 736127 ns/iter (± 18592) 743708 ns/iter (± 14226) 0.99
partial_rule_depth/20 2054771 ns/iter (± 3510) 2071291 ns/iter (± 6065) 0.99
partial_rule_depth/40 7520758 ns/iter (± 33517) 7691108 ns/iter (± 175483) 0.98
partial_rule_depth/80 45280136 ns/iter (± 290490) 47960363 ns/iter (± 364464) 0.94
partial_rule_depth/100 85153217 ns/iter (± 537303) 88405947 ns/iter (± 660868) 0.96

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.