forked from M-Labs/nac3
David Mak
08a7d01a13
Temporarily disable linalg ndarray tests as they are not ported to work with strided-ndarray.
1576 lines
67 KiB
Rust
1576 lines
67 KiB
Rust
use std::{
|
|
collections::{HashMap, HashSet},
|
|
sync::{
|
|
atomic::{AtomicBool, Ordering::Relaxed},
|
|
Arc,
|
|
},
|
|
};
|
|
|
|
use itertools::Itertools;
|
|
use parking_lot::RwLock;
|
|
use pyo3::{
|
|
types::{PyDict, PyTuple},
|
|
PyAny, PyObject, PyResult, Python,
|
|
};
|
|
|
|
use super::PrimitivePythonId;
|
|
use nac3core::{
|
|
codegen::{
|
|
types::{ndarray::NDArrayType, ProxyType},
|
|
CodeGenContext, CodeGenerator,
|
|
},
|
|
inkwell::{
|
|
module::Linkage,
|
|
types::{BasicType, BasicTypeEnum},
|
|
values::BasicValueEnum,
|
|
AddressSpace,
|
|
},
|
|
nac3parser::ast::{self, StrRef},
|
|
symbol_resolver::{StaticValue, SymbolResolver, SymbolValue, ValueEnum},
|
|
toplevel::{
|
|
helper::PrimDef,
|
|
numpy::{make_ndarray_ty, unpack_ndarray_var_tys},
|
|
DefinitionId, TopLevelDef,
|
|
},
|
|
typecheck::{
|
|
type_inferencer::PrimitiveStore,
|
|
typedef::{into_var_map, iter_type_vars, Type, TypeEnum, TypeVar, Unifier, VarMap},
|
|
},
|
|
};
|
|
|
|
pub enum PrimitiveValue {
|
|
I32(i32),
|
|
I64(i64),
|
|
U32(u32),
|
|
U64(u64),
|
|
F64(f64),
|
|
Bool(bool),
|
|
Str(String),
|
|
}
|
|
|
|
/// An entry in the [`DeferredEvaluationStore`], containing the deferred types, a [`PyObject`]
|
|
/// representing the `__constraints__` of the type variables, and the name of the type to be
|
|
/// instantiated.
|
|
type DeferredEvaluationEntry = (Vec<Type>, PyObject, String);
|
|
|
|
#[derive(Clone)]
|
|
pub struct DeferredEvaluationStore {
|
|
needs_defer: Arc<AtomicBool>,
|
|
store: Arc<RwLock<Vec<DeferredEvaluationEntry>>>,
|
|
}
|
|
|
|
impl DeferredEvaluationStore {
|
|
pub fn new() -> Self {
|
|
DeferredEvaluationStore {
|
|
needs_defer: Arc::new(AtomicBool::new(true)),
|
|
store: Arc::new(RwLock::new(Vec::new())),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A class field as stored in the [`InnerResolver`], represented by the ID and name of the
|
|
/// associated [`PythonValue`].
|
|
type ResolverField = (u64, StrRef);
|
|
/// A class field as stored in Python, represented by the `id()` and [`PyObject`] of the field.
|
|
type PyFieldHandle = (u64, PyObject);
|
|
|
|
pub struct InnerResolver {
|
|
pub id_to_type: RwLock<HashMap<StrRef, Type>>,
|
|
pub id_to_def: RwLock<HashMap<StrRef, DefinitionId>>,
|
|
pub id_to_pyval: RwLock<HashMap<StrRef, (u64, PyObject)>>,
|
|
pub id_to_primitive: RwLock<HashMap<u64, PrimitiveValue>>,
|
|
pub field_to_val: RwLock<HashMap<ResolverField, Option<PyFieldHandle>>>,
|
|
pub global_value_ids: Arc<RwLock<HashMap<u64, PyObject>>>,
|
|
pub pyid_to_def: Arc<RwLock<HashMap<u64, DefinitionId>>>,
|
|
pub pyid_to_type: Arc<RwLock<HashMap<u64, Type>>>,
|
|
pub primitive_ids: PrimitivePythonId,
|
|
pub helper: PythonHelper,
|
|
pub string_store: Arc<RwLock<HashMap<String, i32>>>,
|
|
pub exception_ids: Arc<RwLock<HashMap<usize, usize>>>,
|
|
pub deferred_eval_store: DeferredEvaluationStore,
|
|
// module specific
|
|
pub name_to_pyid: HashMap<StrRef, u64>,
|
|
pub module: PyObject,
|
|
}
|
|
|
|
pub struct Resolver(pub Arc<InnerResolver>);
|
|
|
|
#[derive(Clone)]
|
|
pub struct PythonHelper {
|
|
pub type_fn: PyObject,
|
|
pub len_fn: PyObject,
|
|
pub id_fn: PyObject,
|
|
pub origin_ty_fn: PyObject,
|
|
pub args_ty_fn: PyObject,
|
|
pub store_obj: PyObject,
|
|
pub store_str: PyObject,
|
|
}
|
|
|
|
struct PythonValue {
|
|
id: u64,
|
|
value: PyObject,
|
|
store_obj: PyObject,
|
|
resolver: Arc<InnerResolver>,
|
|
}
|
|
|
|
impl StaticValue for PythonValue {
|
|
fn get_unique_identifier(&self) -> u64 {
|
|
self.id
|
|
}
|
|
|
|
fn get_const_obj<'ctx>(
|
|
&self,
|
|
ctx: &mut CodeGenContext<'ctx, '_>,
|
|
_: &mut dyn CodeGenerator,
|
|
) -> BasicValueEnum<'ctx> {
|
|
ctx.module.get_global(format!("{}_const", self.id).as_str()).map_or_else(
|
|
|| {
|
|
Python::with_gil(|py| -> PyResult<BasicValueEnum<'ctx>> {
|
|
let id: u32 = self.store_obj.call1(py, (self.value.clone(),))?.extract(py)?;
|
|
let struct_type = ctx.ctx.struct_type(&[ctx.ctx.i32_type().into()], false);
|
|
let global = ctx.module.add_global(
|
|
struct_type,
|
|
None,
|
|
format!("{}_const", self.id).as_str(),
|
|
);
|
|
global.set_constant(true);
|
|
// Set linkage of global to private to avoid name collisions
|
|
global.set_linkage(Linkage::Private);
|
|
global.set_initializer(&ctx.ctx.const_struct(
|
|
&[ctx.ctx.i32_type().const_int(u64::from(id), false).into()],
|
|
false,
|
|
));
|
|
Ok(global.as_pointer_value().into())
|
|
})
|
|
.unwrap()
|
|
},
|
|
|val| val.as_pointer_value().into(),
|
|
)
|
|
}
|
|
|
|
fn to_basic_value_enum<'ctx, 'a>(
|
|
&self,
|
|
ctx: &mut CodeGenContext<'ctx, 'a>,
|
|
generator: &mut dyn CodeGenerator,
|
|
expected_ty: Type,
|
|
) -> Result<BasicValueEnum<'ctx>, String> {
|
|
if let Some(val) = self.resolver.id_to_primitive.read().get(&self.id) {
|
|
return Ok(match val {
|
|
PrimitiveValue::I32(val) => ctx.ctx.i32_type().const_int(*val as u64, false).into(),
|
|
PrimitiveValue::I64(val) => ctx.ctx.i64_type().const_int(*val as u64, false).into(),
|
|
PrimitiveValue::U32(val) => {
|
|
ctx.ctx.i32_type().const_int(u64::from(*val), false).into()
|
|
}
|
|
PrimitiveValue::U64(val) => ctx.ctx.i64_type().const_int(*val, false).into(),
|
|
PrimitiveValue::F64(val) => ctx.ctx.f64_type().const_float(*val).into(),
|
|
PrimitiveValue::Bool(val) => {
|
|
ctx.ctx.i8_type().const_int(u64::from(*val), false).into()
|
|
}
|
|
PrimitiveValue::Str(val) => ctx.gen_string(generator, val).into(),
|
|
});
|
|
}
|
|
if let Some(global) = ctx.module.get_global(&self.id.to_string()) {
|
|
return Ok(global.as_pointer_value().into());
|
|
}
|
|
|
|
Python::with_gil(|py| -> PyResult<BasicValueEnum<'ctx>> {
|
|
self.resolver
|
|
.get_obj_value(py, self.value.as_ref(py), ctx, generator, expected_ty)
|
|
.map(Option::unwrap)
|
|
})
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
fn get_field<'ctx>(
|
|
&self,
|
|
name: StrRef,
|
|
ctx: &mut CodeGenContext<'ctx, '_>,
|
|
) -> Option<ValueEnum<'ctx>> {
|
|
{
|
|
let field_to_val = self.resolver.field_to_val.read();
|
|
field_to_val.get(&(self.id, name)).cloned()
|
|
}
|
|
.unwrap_or_else(|| {
|
|
Python::with_gil(|py| -> PyResult<Option<(u64, PyObject)>> {
|
|
let helper = &self.resolver.helper;
|
|
let ty = helper.type_fn.call1(py, (&self.value,))?;
|
|
let ty_id: u64 = helper.id_fn.call1(py, (ty,))?.extract(py)?;
|
|
// for optimizing unwrap KernelInvariant
|
|
if ty_id == self.resolver.primitive_ids.option && name == "_nac3_option".into() {
|
|
let obj = self.value.getattr(py, name.to_string().as_str())?;
|
|
let id = self.resolver.helper.id_fn.call1(py, (&obj,))?.extract(py)?;
|
|
return if self.id == self.resolver.primitive_ids.none {
|
|
Ok(None)
|
|
} else {
|
|
Ok(Some((id, obj)))
|
|
};
|
|
}
|
|
let def_id = { *self.resolver.pyid_to_def.read().get(&ty_id).unwrap() };
|
|
let mut mutable = true;
|
|
let defs = ctx.top_level.definitions.read();
|
|
if let TopLevelDef::Class { fields, .. } = &*defs[def_id.0].read() {
|
|
for (field_name, _, is_mutable) in fields {
|
|
if field_name == &name {
|
|
mutable = *is_mutable;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
let result = if mutable {
|
|
None
|
|
} else {
|
|
let obj = self.value.getattr(py, name.to_string().as_str())?;
|
|
let id = self.resolver.helper.id_fn.call1(py, (&obj,))?.extract(py)?;
|
|
Some((id, obj))
|
|
};
|
|
self.resolver.field_to_val.write().insert((self.id, name), result.clone());
|
|
Ok(result)
|
|
})
|
|
.unwrap()
|
|
})
|
|
.map(|(id, obj)| {
|
|
ValueEnum::Static(Arc::new(PythonValue {
|
|
id,
|
|
value: obj,
|
|
store_obj: self.store_obj.clone(),
|
|
resolver: self.resolver.clone(),
|
|
}))
|
|
})
|
|
}
|
|
|
|
fn get_tuple_element<'ctx>(&self, index: u32) -> Option<ValueEnum<'ctx>> {
|
|
Python::with_gil(|py| -> PyResult<Option<(u64, PyObject)>> {
|
|
let helper = &self.resolver.helper;
|
|
let ty = helper.type_fn.call1(py, (&self.value,))?;
|
|
let ty_id: u64 = helper.id_fn.call1(py, (ty,))?.extract(py)?;
|
|
assert_eq!(ty_id, self.resolver.primitive_ids.tuple);
|
|
let tup: &PyTuple = self.value.extract(py)?;
|
|
let elem = tup.get_item(index as usize)?;
|
|
let id = self.resolver.helper.id_fn.call1(py, (elem,))?.extract(py)?;
|
|
Ok(Some((id, elem.into())))
|
|
})
|
|
.unwrap()
|
|
.map(|(id, obj)| {
|
|
ValueEnum::Static(Arc::new(PythonValue {
|
|
id,
|
|
value: obj,
|
|
store_obj: self.store_obj.clone(),
|
|
resolver: self.resolver.clone(),
|
|
}))
|
|
})
|
|
}
|
|
}
|
|
|
|
impl InnerResolver {
|
|
fn get_list_elem_type(
|
|
&self,
|
|
py: Python,
|
|
list: &PyAny,
|
|
len: usize,
|
|
unifier: &mut Unifier,
|
|
defs: &[Arc<RwLock<TopLevelDef>>],
|
|
primitives: &PrimitiveStore,
|
|
) -> PyResult<Result<Type, String>> {
|
|
let mut ty = match self.get_obj_type(py, list.get_item(0)?, unifier, defs, primitives)? {
|
|
Ok(t) => t,
|
|
Err(e) => return Ok(Err(format!("type error ({e}) at element #0 of the list"))),
|
|
};
|
|
for i in 1..len {
|
|
let b = match list
|
|
.get_item(i)
|
|
.map(|elem| self.get_obj_type(py, elem, unifier, defs, primitives))??
|
|
{
|
|
Ok(t) => t,
|
|
Err(e) => return Ok(Err(format!("type error ({e}) at element #{i} of the list"))),
|
|
};
|
|
ty = match unifier.unify(ty, b) {
|
|
Ok(()) => ty,
|
|
Err(e) => {
|
|
return Ok(Err(format!(
|
|
"inhomogeneous type ({}) at element #{i} of the list",
|
|
e.to_display(unifier)
|
|
)))
|
|
}
|
|
};
|
|
}
|
|
Ok(Ok(ty))
|
|
}
|
|
|
|
/// Handles python objects that represent types themselves,
|
|
///
|
|
/// Primitives and class types should be themselves, use `ty_id` to check;
|
|
/// `TypeVars` and `GenericAlias`(`A[int, bool]`) should use `ty_ty_id` to check.
|
|
///
|
|
/// The `bool` value returned indicates whether they are instantiated or not
|
|
fn get_pyty_obj_type(
|
|
&self,
|
|
py: Python,
|
|
pyty: &PyAny,
|
|
unifier: &mut Unifier,
|
|
defs: &[Arc<RwLock<TopLevelDef>>],
|
|
primitives: &PrimitiveStore,
|
|
) -> PyResult<Result<(Type, bool), String>> {
|
|
let ty_id: u64 = self.helper.id_fn.call1(py, (pyty,))?.extract(py)?;
|
|
let ty_ty_id: u64 =
|
|
self.helper.id_fn.call1(py, (self.helper.type_fn.call1(py, (pyty,))?,))?.extract(py)?;
|
|
|
|
if ty_id == self.primitive_ids.int || ty_id == self.primitive_ids.int32 {
|
|
Ok(Ok((primitives.int32, true)))
|
|
} else if ty_id == self.primitive_ids.int64 {
|
|
Ok(Ok((primitives.int64, true)))
|
|
} else if ty_id == self.primitive_ids.uint32 {
|
|
Ok(Ok((primitives.uint32, true)))
|
|
} else if ty_id == self.primitive_ids.uint64 {
|
|
Ok(Ok((primitives.uint64, true)))
|
|
} else if ty_id == self.primitive_ids.bool || ty_id == self.primitive_ids.np_bool_ {
|
|
Ok(Ok((primitives.bool, true)))
|
|
} else if ty_id == self.primitive_ids.string || ty_id == self.primitive_ids.np_str_ {
|
|
Ok(Ok((primitives.str, true)))
|
|
} else if ty_id == self.primitive_ids.float || ty_id == self.primitive_ids.float64 {
|
|
Ok(Ok((primitives.float, true)))
|
|
} else if ty_id == self.primitive_ids.exception {
|
|
Ok(Ok((primitives.exception, true)))
|
|
} else if ty_id == self.primitive_ids.list {
|
|
// do not handle type var param and concrete check here
|
|
let list_tvar = if let TypeEnum::TObj { obj_id, params, .. } =
|
|
&*unifier.get_ty_immutable(primitives.list)
|
|
{
|
|
assert_eq!(*obj_id, PrimDef::List.id());
|
|
iter_type_vars(params).nth(0).unwrap()
|
|
} else {
|
|
unreachable!()
|
|
};
|
|
|
|
let var = unifier.get_dummy_var().ty;
|
|
let list = unifier
|
|
.subst(primitives.list, &into_var_map([TypeVar { id: list_tvar.id, ty: var }]))
|
|
.unwrap();
|
|
Ok(Ok((list, false)))
|
|
} else if ty_id == self.primitive_ids.ndarray {
|
|
// do not handle type var param and concrete check here
|
|
let var = unifier.get_dummy_var().ty;
|
|
let ndims = unifier.get_fresh_const_generic_var(primitives.usize(), None, None).ty;
|
|
let ndarray = make_ndarray_ty(unifier, primitives, Some(var), Some(ndims));
|
|
Ok(Ok((ndarray, false)))
|
|
} else if ty_id == self.primitive_ids.tuple {
|
|
// do not handle type var param and concrete check here
|
|
Ok(Ok((unifier.add_ty(TypeEnum::TTuple { ty: vec![], is_vararg_ctx: false }), false)))
|
|
} else if ty_id == self.primitive_ids.option {
|
|
Ok(Ok((primitives.option, false)))
|
|
} else if ty_id == self.primitive_ids.none {
|
|
unreachable!("none cannot be typeid")
|
|
} else if let Some(def_id) = self.pyid_to_def.read().get(&ty_id).copied() {
|
|
let def = defs[def_id.0].read();
|
|
let TopLevelDef::Class { object_id, type_vars, fields, methods, .. } = &*def else {
|
|
// only object is supported, functions are not supported
|
|
unreachable!("function type is not supported, should not be queried")
|
|
};
|
|
|
|
// do not handle type var param and concrete check here, and no subst
|
|
Ok(Ok({
|
|
let ty = TypeEnum::TObj {
|
|
obj_id: *object_id,
|
|
params: type_vars
|
|
.iter()
|
|
.map(|x| {
|
|
let TypeEnum::TVar { id, .. } = &*unifier.get_ty(*x) else {
|
|
unreachable!()
|
|
};
|
|
|
|
(*id, *x)
|
|
})
|
|
.collect(),
|
|
fields: {
|
|
let mut res = methods
|
|
.iter()
|
|
.map(|(iden, ty, _)| (*iden, (*ty, false)))
|
|
.collect::<HashMap<_, _>>();
|
|
res.extend(fields.clone().into_iter().map(|x| (x.0, (x.1, x.2))));
|
|
res
|
|
},
|
|
};
|
|
// here also false, later instantiation use python object to check compatible
|
|
(unifier.add_ty(ty), false)
|
|
}))
|
|
} else if ty_ty_id == self.primitive_ids.typevar {
|
|
let name: &str = pyty.getattr("__name__").unwrap().extract().unwrap();
|
|
let (constraint_types, is_const_generic) = {
|
|
let constraints = pyty.getattr("__constraints__").unwrap();
|
|
let mut result: Vec<Type> = vec![];
|
|
let needs_defer = self.deferred_eval_store.needs_defer.load(Relaxed);
|
|
|
|
let mut is_const_generic = false;
|
|
for i in 0usize.. {
|
|
if let Ok(constr) = constraints.get_item(i) {
|
|
let constr_id: u64 = self.helper.id_fn.call1(py, (constr,))?.extract(py)?;
|
|
if constr_id == self.primitive_ids.const_generic_marker {
|
|
is_const_generic = true;
|
|
continue;
|
|
}
|
|
|
|
if !is_const_generic && needs_defer {
|
|
result.push(unifier.get_dummy_var().ty);
|
|
} else {
|
|
result.push({
|
|
match self.get_pyty_obj_type(py, constr, unifier, defs, primitives)? {
|
|
Ok((ty, _)) => {
|
|
if unifier.is_concrete(ty, &[]) {
|
|
ty
|
|
} else {
|
|
return Ok(Err(format!(
|
|
"the {}th constraint of TypeVar `{}` is not concrete",
|
|
i + 1,
|
|
pyty.getattr("__name__")?.extract::<String>()?
|
|
)));
|
|
}
|
|
}
|
|
Err(err) => return Ok(Err(err)),
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if !is_const_generic && needs_defer {
|
|
self.deferred_eval_store.store.write().push((
|
|
result.clone(),
|
|
constraints.extract()?,
|
|
pyty.getattr("__name__")?.extract::<String>()?,
|
|
));
|
|
}
|
|
|
|
(result, is_const_generic)
|
|
};
|
|
|
|
let res = if is_const_generic {
|
|
if constraint_types.len() != 1 {
|
|
return Ok(Err(format!(
|
|
"ConstGeneric expects 1 argument, got {}",
|
|
constraint_types.len()
|
|
)));
|
|
}
|
|
|
|
unifier.get_fresh_const_generic_var(constraint_types[0], Some(name.into()), None).ty
|
|
} else {
|
|
unifier.get_fresh_var_with_range(&constraint_types, Some(name.into()), None).ty
|
|
};
|
|
|
|
Ok(Ok((res, true)))
|
|
} else if ty_ty_id == self.primitive_ids.generic_alias.0
|
|
|| ty_ty_id == self.primitive_ids.generic_alias.1
|
|
{
|
|
let origin = self.helper.origin_ty_fn.call1(py, (pyty,))?;
|
|
let args = self.helper.args_ty_fn.call1(py, (pyty,))?;
|
|
let args: &PyTuple = args.downcast(py)?;
|
|
let origin_ty =
|
|
match self.get_pyty_obj_type(py, origin.as_ref(py), unifier, defs, primitives)? {
|
|
Ok((ty, false)) => ty,
|
|
Ok((_, true)) => {
|
|
return Ok(Err("instantiated type does not take type parameters".into()))
|
|
}
|
|
Err(err) => return Ok(Err(err)),
|
|
};
|
|
|
|
match &*unifier.get_ty(origin_ty) {
|
|
TypeEnum::TObj { obj_id, .. } if *obj_id == PrimDef::List.id() => {
|
|
if args.len() == 1 {
|
|
let ty = match self.get_pyty_obj_type(
|
|
py,
|
|
args.get_item(0)?,
|
|
unifier,
|
|
defs,
|
|
primitives,
|
|
)? {
|
|
Ok(ty) => ty,
|
|
Err(err) => return Ok(Err(err)),
|
|
};
|
|
if !unifier.is_concrete(ty.0, &[]) && !ty.1 {
|
|
return Ok(Err(
|
|
"type list should take concrete parameters in typevar range".into(),
|
|
));
|
|
}
|
|
let list_tvar = if let TypeEnum::TObj { obj_id, params, .. } =
|
|
&*unifier.get_ty_immutable(primitives.list)
|
|
{
|
|
assert_eq!(*obj_id, PrimDef::List.id());
|
|
iter_type_vars(params).nth(0).unwrap()
|
|
} else {
|
|
unreachable!()
|
|
};
|
|
let list = unifier
|
|
.subst(
|
|
primitives.list,
|
|
&into_var_map([TypeVar { id: list_tvar.id, ty: ty.0 }]),
|
|
)
|
|
.unwrap();
|
|
Ok(Ok((list, true)))
|
|
} else {
|
|
return Ok(Err(format!(
|
|
"type list needs exactly 1 type parameters, found {}",
|
|
args.len()
|
|
)));
|
|
}
|
|
}
|
|
TypeEnum::TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id() => {
|
|
if args.len() != 2 {
|
|
return Ok(Err(format!(
|
|
"type list needs exactly 2 type parameters, found {}",
|
|
args.len()
|
|
)));
|
|
}
|
|
|
|
// npt.NDArray[T] == np.ndarray[Any, np.dtype[T]]
|
|
let ndarray_dtype_pyty =
|
|
self.helper.args_ty_fn.call1(py, (args.get_item(1)?,))?;
|
|
let dtype = ndarray_dtype_pyty.downcast::<PyTuple>(py)?.get_item(0)?;
|
|
|
|
let ty = match self.get_pyty_obj_type(py, dtype, unifier, defs, primitives)? {
|
|
Ok(ty) => ty,
|
|
Err(err) => return Ok(Err(err)),
|
|
};
|
|
|
|
if !unifier.is_concrete(ty.0, &[]) && !ty.1 {
|
|
return Ok(Err(
|
|
"type `ndarray` should take concrete parameters for dtype".into()
|
|
));
|
|
}
|
|
|
|
Ok(Ok((make_ndarray_ty(unifier, primitives, Some(ty.0), None), true)))
|
|
}
|
|
TypeEnum::TTuple { .. } => {
|
|
let args = match args
|
|
.iter()
|
|
.map(|x| self.get_pyty_obj_type(py, x, unifier, defs, primitives))
|
|
.collect::<Result<Vec<_>, _>>()?
|
|
.into_iter()
|
|
.collect::<Result<Vec<_>, _>>() {
|
|
Ok(args) if !args.is_empty() => args
|
|
.into_iter()
|
|
.map(|(x, check)| if !unifier.is_concrete(x, &[]) && !check {
|
|
panic!("type tuple should take concrete parameters in type var ranges")
|
|
} else {
|
|
x
|
|
}
|
|
)
|
|
.collect::<Vec<_>>(),
|
|
Err(err) => return Ok(Err(err)),
|
|
_ => return Ok(Err("tuple type needs at least 1 type parameters".to_string()))
|
|
};
|
|
Ok(Ok((
|
|
unifier.add_ty(TypeEnum::TTuple { ty: args, is_vararg_ctx: false }),
|
|
true,
|
|
)))
|
|
}
|
|
TypeEnum::TObj { params, obj_id, .. } => {
|
|
let subst = {
|
|
if params.len() != args.len() {
|
|
return Ok(Err(format!(
|
|
"for class #{}, expect {} type parameters, got {}.",
|
|
obj_id.0,
|
|
params.len(),
|
|
args.len(),
|
|
)));
|
|
}
|
|
let args = match args
|
|
.iter()
|
|
.map(|x| self.get_pyty_obj_type(py, x, unifier, defs, primitives))
|
|
.collect::<Result<Vec<_>, _>>()?
|
|
.into_iter()
|
|
.collect::<Result<Vec<_>, _>>() {
|
|
Ok(args) => args
|
|
.into_iter()
|
|
.map(|(x, check)| if !unifier.is_concrete(x, &[]) && !check {
|
|
panic!("type class should take concrete parameters in type var ranges")
|
|
} else {
|
|
x
|
|
}
|
|
)
|
|
.collect::<Vec<_>>(),
|
|
Err(err) => return Ok(Err(err)),
|
|
};
|
|
params
|
|
.iter()
|
|
.zip(args.iter())
|
|
.map(|((id, _), ty)| (*id, *ty))
|
|
.collect::<VarMap>()
|
|
};
|
|
Ok(Ok((unifier.subst(origin_ty, &subst).unwrap_or(origin_ty), true)))
|
|
}
|
|
TypeEnum::TVirtual { .. } => {
|
|
if args.len() == 1 {
|
|
let ty = match self.get_pyty_obj_type(
|
|
py,
|
|
args.get_item(0)?,
|
|
unifier,
|
|
defs,
|
|
primitives,
|
|
)? {
|
|
Ok(ty) => ty,
|
|
Err(err) => return Ok(Err(err)),
|
|
};
|
|
assert!(
|
|
unifier.is_concrete(ty.0, &[]) || ty.1,
|
|
"virtual class should take concrete parameters in type var ranges"
|
|
);
|
|
Ok(Ok((unifier.add_ty(TypeEnum::TVirtual { ty: ty.0 }), true)))
|
|
} else {
|
|
return Ok(Err(format!(
|
|
"virtual class needs exactly 1 type parameters, found {}",
|
|
args.len()
|
|
)));
|
|
}
|
|
}
|
|
_ => unimplemented!(),
|
|
}
|
|
} else if ty_id == self.primitive_ids.virtual_id {
|
|
Ok(Ok((
|
|
{
|
|
let ty = TypeEnum::TVirtual { ty: unifier.get_dummy_var().ty };
|
|
unifier.add_ty(ty)
|
|
},
|
|
false,
|
|
)))
|
|
} else {
|
|
let str_fn =
|
|
pyo3::types::PyModule::import(py, "builtins").unwrap().getattr("repr").unwrap();
|
|
let str_repr: String = str_fn.call1((pyty,)).unwrap().extract().unwrap();
|
|
Ok(Err(format!("{str_repr} is not registered with NAC3 (@nac3 decorator missing?)")))
|
|
}
|
|
}
|
|
|
|
pub fn get_obj_type(
|
|
&self,
|
|
py: Python,
|
|
obj: &PyAny,
|
|
unifier: &mut Unifier,
|
|
defs: &[Arc<RwLock<TopLevelDef>>],
|
|
primitives: &PrimitiveStore,
|
|
) -> PyResult<Result<Type, String>> {
|
|
let ty = self.helper.type_fn.call1(py, (obj,)).unwrap();
|
|
let py_obj_id: u64 = self.helper.id_fn.call1(py, (obj,))?.extract(py)?;
|
|
if let Some(ty) = self.pyid_to_type.read().get(&py_obj_id) {
|
|
return Ok(Ok(*ty));
|
|
}
|
|
|
|
// check if constructor function exists in the methods list
|
|
let pyid_to_def = self.pyid_to_def.read();
|
|
let constructor_ty = pyid_to_def.get(&py_obj_id).and_then(|def_id| {
|
|
defs.iter().find_map(|def| {
|
|
if let Some(rear_guard) = def.try_read() {
|
|
if let TopLevelDef::Class { object_id, methods, constructor, .. } = &*rear_guard
|
|
{
|
|
if object_id == def_id
|
|
&& constructor.is_some()
|
|
&& methods.iter().any(|(s, _, _)| s == &"__init__".into())
|
|
{
|
|
return *constructor;
|
|
}
|
|
}
|
|
}
|
|
None
|
|
})
|
|
});
|
|
|
|
if let Some(ty) = constructor_ty {
|
|
self.pyid_to_type.write().insert(py_obj_id, ty);
|
|
return Ok(Ok(ty));
|
|
}
|
|
|
|
let (extracted_ty, inst_check) = match self.get_pyty_obj_type(
|
|
py,
|
|
{
|
|
if [
|
|
self.primitive_ids.typevar,
|
|
self.primitive_ids.generic_alias.0,
|
|
self.primitive_ids.generic_alias.1,
|
|
]
|
|
.contains(&self.helper.id_fn.call1(py, (ty.clone(),))?.extract::<u64>(py)?)
|
|
{
|
|
obj
|
|
} else {
|
|
ty.as_ref(py)
|
|
}
|
|
},
|
|
unifier,
|
|
defs,
|
|
primitives,
|
|
)? {
|
|
Ok(s) => s,
|
|
Err(e) => {
|
|
// Allow access to Class Attributes of Classes without having to initialize Objects
|
|
if self.pyid_to_def.read().contains_key(&py_obj_id) {
|
|
if let Some(def_id) = self.pyid_to_def.read().get(&py_obj_id).copied() {
|
|
let def = defs[def_id.0].read();
|
|
let TopLevelDef::Class { object_id, .. } = &*def else {
|
|
// only object is supported, functions are not supported
|
|
unreachable!("function type is not supported, should not be queried")
|
|
};
|
|
|
|
let ty = TypeEnum::TObj {
|
|
obj_id: *object_id,
|
|
params: VarMap::new(),
|
|
fields: HashMap::new(),
|
|
};
|
|
(unifier.add_ty(ty), true)
|
|
} else {
|
|
return Ok(Err(e));
|
|
}
|
|
} else {
|
|
return Ok(Err(e));
|
|
}
|
|
}
|
|
};
|
|
match (&*unifier.get_ty(extracted_ty), inst_check) {
|
|
// do the instantiation for these four types
|
|
(TypeEnum::TObj { obj_id, params, .. }, false) if *obj_id == PrimDef::List.id() => {
|
|
let ty = iter_type_vars(params).nth(0).unwrap().ty;
|
|
let len: usize = self.helper.len_fn.call1(py, (obj,))?.extract(py)?;
|
|
if len == 0 {
|
|
assert!(matches!(
|
|
&*unifier.get_ty(ty),
|
|
TypeEnum::TVar { fields: None, range, .. }
|
|
if range.is_empty()
|
|
));
|
|
Ok(Ok(extracted_ty))
|
|
} else {
|
|
let actual_ty =
|
|
self.get_list_elem_type(py, obj, len, unifier, defs, primitives)?;
|
|
match actual_ty {
|
|
Ok(t) => match unifier.unify(ty, t) {
|
|
Ok(()) => {
|
|
let list_tvar = if let TypeEnum::TObj { obj_id, params, .. } =
|
|
&*unifier.get_ty_immutable(primitives.list)
|
|
{
|
|
assert_eq!(*obj_id, PrimDef::List.id());
|
|
iter_type_vars(params).nth(0).unwrap()
|
|
} else {
|
|
unreachable!()
|
|
};
|
|
let list = unifier
|
|
.subst(
|
|
primitives.list,
|
|
&into_var_map([TypeVar { id: list_tvar.id, ty }]),
|
|
)
|
|
.unwrap();
|
|
Ok(Ok(list))
|
|
}
|
|
|
|
Err(e) => Ok(Err(format!(
|
|
"type error ({}) for the list",
|
|
e.to_display(unifier)
|
|
))),
|
|
},
|
|
Err(e) => Ok(Err(e)),
|
|
}
|
|
}
|
|
}
|
|
(TypeEnum::TObj { obj_id, .. }, false) if *obj_id == PrimDef::NDArray.id() => {
|
|
let (ty, ndims) = unpack_ndarray_var_tys(unifier, extracted_ty);
|
|
let len: usize = obj.getattr("ndim")?.extract()?;
|
|
if len == 0 {
|
|
assert!(matches!(
|
|
&*unifier.get_ty(ty),
|
|
TypeEnum::TVar { fields: None, range, .. }
|
|
if range.is_empty()
|
|
));
|
|
Ok(Ok(extracted_ty))
|
|
} else {
|
|
let dtype = obj.getattr("dtype")?.getattr("type")?;
|
|
let dtype_ty = self.get_pyty_obj_type(py, dtype, unifier, defs, primitives)?;
|
|
match dtype_ty {
|
|
Ok((t, _)) => match unifier.unify(ty, t) {
|
|
Ok(()) => {
|
|
let ndarray_ty =
|
|
make_ndarray_ty(unifier, primitives, Some(ty), Some(ndims));
|
|
|
|
Ok(Ok(ndarray_ty))
|
|
}
|
|
Err(e) => Ok(Err(format!(
|
|
"type error ({}) for the ndarray",
|
|
e.to_display(unifier),
|
|
))),
|
|
},
|
|
Err(e) => Ok(Err(e)),
|
|
}
|
|
}
|
|
}
|
|
(TypeEnum::TTuple { .. }, false) => {
|
|
let elements: &PyTuple = obj.downcast()?;
|
|
let types: Result<Result<Vec<_>, _>, _> = elements
|
|
.iter()
|
|
.map(|elem| self.get_obj_type(py, elem, unifier, defs, primitives))
|
|
.collect();
|
|
let types = types?;
|
|
Ok(types.map(|types| {
|
|
unifier.add_ty(TypeEnum::TTuple { ty: types, is_vararg_ctx: false })
|
|
}))
|
|
}
|
|
// special handling for option type since its class member layout in python side
|
|
// is special and cannot be mapped directly to a nac3 type as below
|
|
(TypeEnum::TObj { obj_id, params, .. }, false)
|
|
if *obj_id == primitives.option.obj_id(unifier).unwrap() =>
|
|
{
|
|
let Ok(field_data) = obj.getattr("_nac3_option") else {
|
|
unreachable!("cannot be None")
|
|
};
|
|
// if is `none`
|
|
let zelf_id: u64 = self.helper.id_fn.call1(py, (obj,))?.extract(py)?;
|
|
if zelf_id == self.primitive_ids.none {
|
|
let ty_enum = unifier.get_ty_immutable(primitives.option);
|
|
let TypeEnum::TObj { params, .. } = ty_enum.as_ref() else {
|
|
unreachable!("must be tobj")
|
|
};
|
|
|
|
let var_map = into_var_map(iter_type_vars(params).map(|tvar| {
|
|
let TypeEnum::TVar { id, range, name, loc, .. } = &*unifier.get_ty(tvar.ty)
|
|
else {
|
|
unreachable!()
|
|
};
|
|
|
|
assert_eq!(*id, tvar.id);
|
|
let ty = unifier.get_fresh_var_with_range(range, *name, *loc).ty;
|
|
TypeVar { id: *id, ty }
|
|
}));
|
|
return Ok(Ok(unifier.subst(primitives.option, &var_map).unwrap()));
|
|
}
|
|
|
|
let ty = match self.get_obj_type(py, field_data, unifier, defs, primitives)? {
|
|
Ok(t) => t,
|
|
Err(e) => {
|
|
return Ok(Err(format!(
|
|
"error when getting type of the option object ({e})"
|
|
)))
|
|
}
|
|
};
|
|
let new_var_map: VarMap = params.iter().map(|(id, _)| (*id, ty)).collect();
|
|
let res = unifier.subst(extracted_ty, &new_var_map).unwrap_or(extracted_ty);
|
|
Ok(Ok(res))
|
|
}
|
|
(TypeEnum::TObj { params, fields, .. }, false) => {
|
|
self.pyid_to_type.write().insert(py_obj_id, extracted_ty);
|
|
let var_map = into_var_map(iter_type_vars(params).map(|tvar| {
|
|
let TypeEnum::TVar { id, range, name, loc, .. } = &*unifier.get_ty(tvar.ty)
|
|
else {
|
|
unreachable!()
|
|
};
|
|
|
|
assert_eq!(*id, tvar.id);
|
|
let ty = unifier.get_fresh_var_with_range(range, *name, *loc).ty;
|
|
TypeVar { id: *id, ty }
|
|
}));
|
|
let mut instantiate_obj = || {
|
|
// loop through non-function fields of the class to get the instantiated value
|
|
for field in fields {
|
|
let name: String = (*field.0).into();
|
|
if let TypeEnum::TFunc(..) = &*unifier.get_ty(field.1 .0) {
|
|
continue;
|
|
}
|
|
let field_data = match obj.getattr(name.as_str()) {
|
|
Ok(d) => d,
|
|
Err(e) => return Ok(Err(format!("{e}"))),
|
|
};
|
|
let ty =
|
|
match self.get_obj_type(py, field_data, unifier, defs, primitives)? {
|
|
Ok(t) => t,
|
|
Err(e) => {
|
|
return Ok(Err(format!(
|
|
"error when getting type of field `{name}` ({e})"
|
|
)))
|
|
}
|
|
};
|
|
let field_ty = unifier.subst(field.1 .0, &var_map).unwrap_or(field.1 .0);
|
|
if let Err(e) = unifier.unify(ty, field_ty) {
|
|
// field type mismatch
|
|
return Ok(Err(format!(
|
|
"error when getting type of field `{name}` ({})",
|
|
e.to_display(unifier)
|
|
)));
|
|
}
|
|
}
|
|
for ty in var_map.values() {
|
|
// must be concrete type
|
|
if !unifier.is_concrete(*ty, &[]) {
|
|
return Ok(Err("object is not of concrete type".into()));
|
|
}
|
|
}
|
|
let extracted_ty =
|
|
unifier.subst(extracted_ty, &var_map).unwrap_or(extracted_ty);
|
|
Ok(Ok(extracted_ty))
|
|
};
|
|
let result = instantiate_obj();
|
|
// update/remove the cache according to the result
|
|
match result {
|
|
Ok(Ok(ty)) => self.pyid_to_type.write().insert(py_obj_id, ty),
|
|
_ => self.pyid_to_type.write().remove(&py_obj_id),
|
|
};
|
|
result
|
|
}
|
|
_ => {
|
|
// check integer bounds
|
|
if unifier.unioned(extracted_ty, primitives.int32) {
|
|
obj.extract::<i32>().map_or_else(
|
|
|_| Ok(Err(format!("{obj} is not in the range of int32"))),
|
|
|_| Ok(Ok(extracted_ty)),
|
|
)
|
|
} else if unifier.unioned(extracted_ty, primitives.int64) {
|
|
obj.extract::<i64>().map_or_else(
|
|
|_| Ok(Err(format!("{obj} is not in the range of int64"))),
|
|
|_| Ok(Ok(extracted_ty)),
|
|
)
|
|
} else if unifier.unioned(extracted_ty, primitives.uint32) {
|
|
obj.extract::<u32>().map_or_else(
|
|
|_| Ok(Err(format!("{obj} is not in the range of uint32"))),
|
|
|_| Ok(Ok(extracted_ty)),
|
|
)
|
|
} else if unifier.unioned(extracted_ty, primitives.uint64) {
|
|
obj.extract::<u64>().map_or_else(
|
|
|_| Ok(Err(format!("{obj} is not in the range of uint64"))),
|
|
|_| Ok(Ok(extracted_ty)),
|
|
)
|
|
} else if unifier.unioned(extracted_ty, primitives.bool) {
|
|
obj.extract::<bool>().map_or_else(
|
|
|_| Ok(Err(format!("{obj} is not in the range of bool"))),
|
|
|_| Ok(Ok(extracted_ty)),
|
|
)
|
|
} else if unifier.unioned(extracted_ty, primitives.float) {
|
|
obj.extract::<f64>().map_or_else(
|
|
|_| Ok(Err(format!("{obj} is not in the range of float64"))),
|
|
|_| Ok(Ok(extracted_ty)),
|
|
)
|
|
} else {
|
|
Ok(Ok(extracted_ty))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn get_obj_value<'ctx>(
|
|
&self,
|
|
py: Python,
|
|
obj: &PyAny,
|
|
ctx: &mut CodeGenContext<'ctx, '_>,
|
|
generator: &mut dyn CodeGenerator,
|
|
expected_ty: Type,
|
|
) -> PyResult<Option<BasicValueEnum<'ctx>>> {
|
|
let ty_id: u64 =
|
|
self.helper.id_fn.call1(py, (self.helper.type_fn.call1(py, (obj,))?,))?.extract(py)?;
|
|
let id: u64 = self.helper.id_fn.call1(py, (obj,))?.extract(py)?;
|
|
if ty_id == self.primitive_ids.int || ty_id == self.primitive_ids.int32 {
|
|
let val: i32 = obj.extract().unwrap();
|
|
self.id_to_primitive.write().insert(id, PrimitiveValue::I32(val));
|
|
Ok(Some(ctx.ctx.i32_type().const_int(val as u64, false).into()))
|
|
} else if ty_id == self.primitive_ids.int64 {
|
|
let val: i64 = obj.extract().unwrap();
|
|
self.id_to_primitive.write().insert(id, PrimitiveValue::I64(val));
|
|
Ok(Some(ctx.ctx.i64_type().const_int(val as u64, false).into()))
|
|
} else if ty_id == self.primitive_ids.uint32 {
|
|
let val: u32 = obj.extract().unwrap();
|
|
self.id_to_primitive.write().insert(id, PrimitiveValue::U32(val));
|
|
Ok(Some(ctx.ctx.i32_type().const_int(u64::from(val), false).into()))
|
|
} else if ty_id == self.primitive_ids.uint64 {
|
|
let val: u64 = obj.extract().unwrap();
|
|
self.id_to_primitive.write().insert(id, PrimitiveValue::U64(val));
|
|
Ok(Some(ctx.ctx.i64_type().const_int(val, false).into()))
|
|
} else if ty_id == self.primitive_ids.bool || ty_id == self.primitive_ids.np_bool_ {
|
|
let val: bool = obj.extract().unwrap();
|
|
self.id_to_primitive.write().insert(id, PrimitiveValue::Bool(val));
|
|
Ok(Some(ctx.ctx.i8_type().const_int(u64::from(val), false).into()))
|
|
} else if ty_id == self.primitive_ids.string || ty_id == self.primitive_ids.np_str_ {
|
|
let val: String = obj.extract().unwrap();
|
|
self.id_to_primitive.write().insert(id, PrimitiveValue::Str(val.clone()));
|
|
Ok(Some(ctx.gen_string(generator, val).into()))
|
|
} else if ty_id == self.primitive_ids.float || ty_id == self.primitive_ids.float64 {
|
|
let val: f64 = obj.extract().unwrap();
|
|
self.id_to_primitive.write().insert(id, PrimitiveValue::F64(val));
|
|
Ok(Some(ctx.ctx.f64_type().const_float(val).into()))
|
|
} else if ty_id == self.primitive_ids.list {
|
|
let id_str = id.to_string();
|
|
|
|
if let Some(global) = ctx.module.get_global(&id_str) {
|
|
return Ok(Some(global.as_pointer_value().into()));
|
|
}
|
|
|
|
let len: usize = self.helper.len_fn.call1(py, (obj,))?.extract(py)?;
|
|
let elem_ty = match ctx.unifier.get_ty_immutable(expected_ty).as_ref() {
|
|
TypeEnum::TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => {
|
|
iter_type_vars(params).nth(0).unwrap().ty
|
|
}
|
|
_ => unreachable!("must be list"),
|
|
};
|
|
let size_t = generator.get_size_type(ctx.ctx);
|
|
let ty = if len == 0
|
|
&& matches!(&*ctx.unifier.get_ty_immutable(elem_ty), TypeEnum::TVar { .. })
|
|
{
|
|
// The default type for zero-length lists of unknown element type is size_t
|
|
size_t.into()
|
|
} else {
|
|
ctx.get_llvm_type(generator, elem_ty)
|
|
};
|
|
let arr_ty = ctx
|
|
.ctx
|
|
.struct_type(&[ty.ptr_type(AddressSpace::default()).into(), size_t.into()], false);
|
|
|
|
{
|
|
if self.global_value_ids.read().contains_key(&id) {
|
|
let global = ctx.module.get_global(&id_str).unwrap_or_else(|| {
|
|
ctx.module.add_global(arr_ty, Some(AddressSpace::default()), &id_str)
|
|
});
|
|
return Ok(Some(global.as_pointer_value().into()));
|
|
}
|
|
self.global_value_ids.write().insert(id, obj.into());
|
|
}
|
|
|
|
let arr: Result<Option<Vec<_>>, _> = (0..len)
|
|
.map(|i| {
|
|
obj.get_item(i).and_then(|elem| {
|
|
self.get_obj_value(py, elem, ctx, generator, elem_ty).map_err(|e| {
|
|
super::CompileError::new_err(format!("Error getting element {i}: {e}"))
|
|
})
|
|
})
|
|
})
|
|
.collect();
|
|
let arr = arr?.unwrap();
|
|
|
|
let arr_global = ctx.module.add_global(
|
|
ty.array_type(len as u32),
|
|
Some(AddressSpace::default()),
|
|
&(id_str.clone() + "_"),
|
|
);
|
|
let arr: BasicValueEnum = if ty.is_int_type() {
|
|
let arr: Vec<_> = arr.into_iter().map(BasicValueEnum::into_int_value).collect();
|
|
ty.into_int_type().const_array(&arr)
|
|
} else if ty.is_float_type() {
|
|
let arr: Vec<_> = arr.into_iter().map(BasicValueEnum::into_float_value).collect();
|
|
ty.into_float_type().const_array(&arr)
|
|
} else if ty.is_array_type() {
|
|
let arr: Vec<_> = arr.into_iter().map(BasicValueEnum::into_array_value).collect();
|
|
ty.into_array_type().const_array(&arr)
|
|
} else if ty.is_struct_type() {
|
|
let arr: Vec<_> = arr.into_iter().map(BasicValueEnum::into_struct_value).collect();
|
|
ty.into_struct_type().const_array(&arr)
|
|
} else if ty.is_pointer_type() {
|
|
let arr: Vec<_> = arr.into_iter().map(BasicValueEnum::into_pointer_value).collect();
|
|
ty.into_pointer_type().const_array(&arr)
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
.into();
|
|
arr_global.set_initializer(&arr);
|
|
|
|
let val = arr_ty.const_named_struct(&[
|
|
arr_global
|
|
.as_pointer_value()
|
|
.const_cast(ty.ptr_type(AddressSpace::default()))
|
|
.into(),
|
|
size_t.const_int(len as u64, false).into(),
|
|
]);
|
|
|
|
let global = ctx.module.add_global(arr_ty, Some(AddressSpace::default()), &id_str);
|
|
global.set_initializer(&val);
|
|
|
|
Ok(Some(global.as_pointer_value().into()))
|
|
} else if ty_id == self.primitive_ids.ndarray {
|
|
let id_str = id.to_string();
|
|
|
|
if let Some(global) = ctx.module.get_global(&id_str) {
|
|
return Ok(Some(global.as_pointer_value().into()));
|
|
}
|
|
|
|
let ndarray_ty = if matches!(&*ctx.unifier.get_ty_immutable(expected_ty), TypeEnum::TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id())
|
|
{
|
|
expected_ty
|
|
} else {
|
|
unreachable!("must be ndarray")
|
|
};
|
|
let (ndarray_dtype, _) = unpack_ndarray_var_tys(&mut ctx.unifier, ndarray_ty);
|
|
|
|
let llvm_usize = generator.get_size_type(ctx.ctx);
|
|
let ndarray_llvm_ty = NDArrayType::from_unifier_type(generator, ctx, ndarray_ty);
|
|
let ndarray_dtype_llvm_ty = ndarray_llvm_ty.element_type();
|
|
|
|
{
|
|
if self.global_value_ids.read().contains_key(&id) {
|
|
let global = ctx.module.get_global(&id_str).unwrap_or_else(|| {
|
|
ctx.module.add_global(
|
|
ndarray_llvm_ty.as_base_type().get_element_type().into_struct_type(),
|
|
Some(AddressSpace::default()),
|
|
&id_str,
|
|
)
|
|
});
|
|
return Ok(Some(global.as_pointer_value().into()));
|
|
}
|
|
self.global_value_ids.write().insert(id, obj.into());
|
|
}
|
|
|
|
let ndarray_ndims = ndarray_llvm_ty.ndims().unwrap();
|
|
|
|
// Obtain the shape of the ndarray
|
|
let shape_tuple: &PyTuple = obj.getattr("shape")?.downcast()?;
|
|
assert_eq!(shape_tuple.len(), ndarray_ndims as usize);
|
|
let shape_values: Result<Option<Vec<_>>, _> = shape_tuple
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, elem)| {
|
|
self.get_obj_value(py, elem, ctx, generator, ctx.primitives.usize()).map_err(
|
|
|e| super::CompileError::new_err(format!("Error getting element {i}: {e}")),
|
|
)
|
|
})
|
|
.collect();
|
|
let shape_values = shape_values?.unwrap();
|
|
let shape_values = llvm_usize.const_array(
|
|
&shape_values.into_iter().map(BasicValueEnum::into_int_value).collect_vec(),
|
|
);
|
|
|
|
// create a global for ndarray.shape and initialize it using the shape
|
|
let shape_global = ctx.module.add_global(
|
|
llvm_usize.array_type(ndarray_ndims as u32),
|
|
Some(AddressSpace::default()),
|
|
&(id_str.clone() + ".shape"),
|
|
);
|
|
shape_global.set_initializer(&shape_values);
|
|
|
|
// Obtain the (flattened) elements of the ndarray
|
|
let sz: usize = obj.getattr("size")?.extract()?;
|
|
let data: Result<Option<Vec<_>>, _> = (0..sz)
|
|
.map(|i| {
|
|
obj.getattr("flat")?.get_item(i).and_then(|elem| {
|
|
self.get_obj_value(py, elem, ctx, generator, ndarray_dtype).map_err(|e| {
|
|
super::CompileError::new_err(format!("Error getting element {i}: {e}"))
|
|
})
|
|
})
|
|
})
|
|
.collect();
|
|
let data = data?.unwrap().into_iter();
|
|
let data = match ndarray_dtype_llvm_ty {
|
|
BasicTypeEnum::ArrayType(ty) => {
|
|
ty.const_array(&data.map(BasicValueEnum::into_array_value).collect_vec())
|
|
}
|
|
|
|
BasicTypeEnum::FloatType(ty) => {
|
|
ty.const_array(&data.map(BasicValueEnum::into_float_value).collect_vec())
|
|
}
|
|
|
|
BasicTypeEnum::IntType(ty) => {
|
|
ty.const_array(&data.map(BasicValueEnum::into_int_value).collect_vec())
|
|
}
|
|
|
|
BasicTypeEnum::PointerType(ty) => {
|
|
ty.const_array(&data.map(BasicValueEnum::into_pointer_value).collect_vec())
|
|
}
|
|
|
|
BasicTypeEnum::StructType(ty) => {
|
|
ty.const_array(&data.map(BasicValueEnum::into_struct_value).collect_vec())
|
|
}
|
|
|
|
BasicTypeEnum::VectorType(_) => unreachable!(),
|
|
};
|
|
|
|
// create a global for ndarray.data and initialize it using the elements
|
|
let data_global = ctx.module.add_global(
|
|
ndarray_dtype_llvm_ty.array_type(sz as u32),
|
|
Some(AddressSpace::default()),
|
|
&(id_str.clone() + ".data"),
|
|
);
|
|
data_global.set_initializer(&data);
|
|
|
|
// create a global for the ndarray object and initialize it
|
|
let value = ndarray_llvm_ty
|
|
.as_base_type()
|
|
.get_element_type()
|
|
.into_struct_type()
|
|
.const_named_struct(&[
|
|
llvm_usize.const_int(ndarray_ndims, false).into(),
|
|
shape_global
|
|
.as_pointer_value()
|
|
.const_cast(llvm_usize.ptr_type(AddressSpace::default()))
|
|
.into(),
|
|
data_global
|
|
.as_pointer_value()
|
|
.const_cast(ndarray_dtype_llvm_ty.ptr_type(AddressSpace::default()))
|
|
.into(),
|
|
]);
|
|
|
|
let ndarray = ctx.module.add_global(
|
|
ndarray_llvm_ty.as_base_type().get_element_type().into_struct_type(),
|
|
Some(AddressSpace::default()),
|
|
&id_str,
|
|
);
|
|
ndarray.set_initializer(&value);
|
|
|
|
Ok(Some(ndarray.as_pointer_value().into()))
|
|
} else if ty_id == self.primitive_ids.tuple {
|
|
let expected_ty_enum = ctx.unifier.get_ty_immutable(expected_ty);
|
|
let TypeEnum::TTuple { ty, is_vararg_ctx: false } = expected_ty_enum.as_ref() else {
|
|
unreachable!()
|
|
};
|
|
|
|
let tup_tys = ty.iter();
|
|
let elements: &PyTuple = obj.downcast()?;
|
|
assert_eq!(elements.len(), tup_tys.len());
|
|
let val: Result<Option<Vec<_>>, _> = elements
|
|
.iter()
|
|
.enumerate()
|
|
.zip(tup_tys)
|
|
.map(|((i, elem), ty)| {
|
|
self.get_obj_value(py, elem, ctx, generator, *ty).map_err(|e| {
|
|
super::CompileError::new_err(format!("Error getting element {i}: {e}"))
|
|
})
|
|
})
|
|
.collect();
|
|
let val = val?.unwrap();
|
|
let val = ctx.ctx.const_struct(&val, false);
|
|
Ok(Some(val.into()))
|
|
} else if ty_id == self.primitive_ids.option {
|
|
let option_val_ty = match ctx.unifier.get_ty_immutable(expected_ty).as_ref() {
|
|
TypeEnum::TObj { obj_id, params, .. }
|
|
if *obj_id == ctx.primitives.option.obj_id(&ctx.unifier).unwrap() =>
|
|
{
|
|
*params.iter().next().unwrap().1
|
|
}
|
|
_ => unreachable!("must be option type"),
|
|
};
|
|
if id == self.primitive_ids.none {
|
|
// for option type, just a null ptr
|
|
Ok(Some(
|
|
ctx.get_llvm_type(generator, option_val_ty)
|
|
.ptr_type(AddressSpace::default())
|
|
.const_null()
|
|
.into(),
|
|
))
|
|
} else {
|
|
match self
|
|
.get_obj_value(
|
|
py,
|
|
obj.getattr("_nac3_option").unwrap(),
|
|
ctx,
|
|
generator,
|
|
option_val_ty,
|
|
)
|
|
.map_err(|e| {
|
|
super::CompileError::new_err(format!(
|
|
"Error getting value of Option object: {e}"
|
|
))
|
|
})? {
|
|
Some(v) => {
|
|
let global_str = format!("{id}_option");
|
|
{
|
|
if self.global_value_ids.read().contains_key(&id) {
|
|
let global =
|
|
ctx.module.get_global(&global_str).unwrap_or_else(|| {
|
|
ctx.module.add_global(
|
|
v.get_type(),
|
|
Some(AddressSpace::default()),
|
|
&global_str,
|
|
)
|
|
});
|
|
return Ok(Some(global.as_pointer_value().into()));
|
|
}
|
|
self.global_value_ids.write().insert(id, obj.into());
|
|
}
|
|
let global = ctx.module.add_global(
|
|
v.get_type(),
|
|
Some(AddressSpace::default()),
|
|
&global_str,
|
|
);
|
|
global.set_initializer(&v);
|
|
Ok(Some(global.as_pointer_value().into()))
|
|
}
|
|
None => Ok(None),
|
|
}
|
|
}
|
|
} else {
|
|
let id_str = id.to_string();
|
|
|
|
if let Some(global) = ctx.module.get_global(&id_str) {
|
|
return Ok(Some(global.as_pointer_value().into()));
|
|
}
|
|
|
|
let top_level_defs = ctx.top_level.definitions.read();
|
|
let ty = self
|
|
.get_obj_type(py, obj, &mut ctx.unifier, &top_level_defs, &ctx.primitives)?
|
|
.unwrap();
|
|
let ty = ctx
|
|
.get_llvm_type(generator, ty)
|
|
.into_pointer_type()
|
|
.get_element_type()
|
|
.into_struct_type();
|
|
{
|
|
if self.global_value_ids.read().contains_key(&id) {
|
|
let global = ctx.module.get_global(&id_str).unwrap_or_else(|| {
|
|
ctx.module.add_global(ty, Some(AddressSpace::default()), &id_str)
|
|
});
|
|
return Ok(Some(global.as_pointer_value().into()));
|
|
}
|
|
self.global_value_ids.write().insert(id, obj.into());
|
|
}
|
|
// should be classes
|
|
let definition =
|
|
top_level_defs.get(self.pyid_to_def.read().get(&ty_id).unwrap().0).unwrap().read();
|
|
let TopLevelDef::Class { fields, .. } = &*definition else { unreachable!() };
|
|
|
|
let values: Result<Option<Vec<_>>, _> = fields
|
|
.iter()
|
|
.map(|(name, ty, _)| {
|
|
self.get_obj_value(
|
|
py,
|
|
obj.getattr(name.to_string().as_str())?,
|
|
ctx,
|
|
generator,
|
|
*ty,
|
|
)
|
|
.map_err(|e| {
|
|
super::CompileError::new_err(format!("Error getting field {name}: {e}"))
|
|
})
|
|
})
|
|
.collect();
|
|
let values = values?;
|
|
if let Some(values) = values {
|
|
let val = ty.const_named_struct(&values);
|
|
let global = ctx.module.get_global(&id_str).unwrap_or_else(|| {
|
|
ctx.module.add_global(ty, Some(AddressSpace::default()), &id_str)
|
|
});
|
|
global.set_initializer(&val);
|
|
Ok(Some(global.as_pointer_value().into()))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_default_param_obj_value(
|
|
&self,
|
|
py: Python,
|
|
obj: &PyAny,
|
|
) -> PyResult<Result<SymbolValue, String>> {
|
|
let id: u64 = self.helper.id_fn.call1(py, (obj,))?.extract(py)?;
|
|
let ty_id: u64 =
|
|
self.helper.id_fn.call1(py, (self.helper.type_fn.call1(py, (obj,))?,))?.extract(py)?;
|
|
Ok(if ty_id == self.primitive_ids.int || ty_id == self.primitive_ids.int32 {
|
|
let val: i32 = obj.extract()?;
|
|
Ok(SymbolValue::I32(val))
|
|
} else if ty_id == self.primitive_ids.int64 {
|
|
let val: i64 = obj.extract()?;
|
|
Ok(SymbolValue::I64(val))
|
|
} else if ty_id == self.primitive_ids.uint32 {
|
|
let val: u32 = obj.extract()?;
|
|
Ok(SymbolValue::U32(val))
|
|
} else if ty_id == self.primitive_ids.uint64 {
|
|
let val: u64 = obj.extract()?;
|
|
Ok(SymbolValue::U64(val))
|
|
} else if ty_id == self.primitive_ids.bool || ty_id == self.primitive_ids.np_bool_ {
|
|
let val: bool = obj.extract()?;
|
|
Ok(SymbolValue::Bool(val))
|
|
} else if ty_id == self.primitive_ids.string || ty_id == self.primitive_ids.np_str_ {
|
|
let val: String = obj.extract()?;
|
|
Ok(SymbolValue::Str(val))
|
|
} else if ty_id == self.primitive_ids.float || ty_id == self.primitive_ids.float64 {
|
|
let val: f64 = obj.extract()?;
|
|
Ok(SymbolValue::Double(val))
|
|
} else if ty_id == self.primitive_ids.tuple {
|
|
let elements: &PyTuple = obj.downcast()?;
|
|
let elements: Result<Result<Vec<_>, String>, _> =
|
|
elements.iter().map(|elem| self.get_default_param_obj_value(py, elem)).collect();
|
|
elements?.map(SymbolValue::Tuple)
|
|
} else if ty_id == self.primitive_ids.option {
|
|
if id == self.primitive_ids.none {
|
|
Ok(SymbolValue::OptionNone)
|
|
} else {
|
|
self.get_default_param_obj_value(py, obj.getattr("_nac3_option").unwrap())?
|
|
.map(|v| SymbolValue::OptionSome(Box::new(v)))
|
|
}
|
|
} else {
|
|
Err("only primitives values, option and tuple can be default parameter value".into())
|
|
})
|
|
}
|
|
}
|
|
|
|
impl SymbolResolver for Resolver {
|
|
fn get_default_param_value(&self, expr: &ast::Expr) -> Option<SymbolValue> {
|
|
let ast::ExprKind::Name { id, .. } = &expr.node else {
|
|
unreachable!("only for resolving names")
|
|
};
|
|
|
|
Python::with_gil(|py| -> PyResult<Option<SymbolValue>> {
|
|
let obj: &PyAny = self.0.module.extract(py)?;
|
|
let members: &PyDict = obj.getattr("__dict__").unwrap().downcast().unwrap();
|
|
let mut sym_value = None;
|
|
for (key, val) in members {
|
|
let key: &str = key.extract()?;
|
|
if key == id.to_string() {
|
|
if let Ok(Ok(v)) = self.0.get_default_param_obj_value(py, val) {
|
|
sym_value = Some(v);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
Ok(sym_value)
|
|
})
|
|
.unwrap()
|
|
}
|
|
|
|
fn get_symbol_type(
|
|
&self,
|
|
unifier: &mut Unifier,
|
|
defs: &[Arc<RwLock<TopLevelDef>>],
|
|
primitives: &PrimitiveStore,
|
|
str: StrRef,
|
|
) -> Result<Type, String> {
|
|
if let Some(ty) = {
|
|
let id_to_type = self.0.id_to_type.read();
|
|
id_to_type.get(&str).copied()
|
|
} {
|
|
Ok(ty)
|
|
} else {
|
|
let Some(id) = self.0.name_to_pyid.get(&str) else {
|
|
return Err(format!("cannot find symbol `{str}`"));
|
|
};
|
|
let result = if let Some(t) = {
|
|
let pyid_to_type = self.0.pyid_to_type.read();
|
|
pyid_to_type.get(id).copied()
|
|
} {
|
|
Ok(t)
|
|
} else {
|
|
Python::with_gil(|py| -> PyResult<Result<Type, String>> {
|
|
let obj: &PyAny = self.0.module.extract(py)?;
|
|
let mut sym_ty = Err(format!("cannot find symbol `{str}`"));
|
|
let members: &PyDict = obj.getattr("__dict__").unwrap().downcast().unwrap();
|
|
for (key, val) in members {
|
|
let key: &str = key.extract()?;
|
|
if key == str.to_string() {
|
|
sym_ty = self.0.get_obj_type(py, val, unifier, defs, primitives)?;
|
|
break;
|
|
}
|
|
}
|
|
if let Ok(t) = sym_ty {
|
|
if let TypeEnum::TVar { .. } = &*unifier.get_ty(t) {
|
|
self.0.pyid_to_type.write().insert(*id, t);
|
|
}
|
|
}
|
|
Ok(sym_ty)
|
|
})
|
|
.unwrap()
|
|
};
|
|
result
|
|
}
|
|
}
|
|
|
|
fn get_symbol_value<'ctx>(
|
|
&self,
|
|
id: StrRef,
|
|
_: &mut CodeGenContext<'ctx, '_>,
|
|
_: &mut dyn CodeGenerator,
|
|
) -> Option<ValueEnum<'ctx>> {
|
|
let sym_value = {
|
|
let id_to_val = self.0.id_to_pyval.read();
|
|
id_to_val.get(&id).cloned()
|
|
}
|
|
.or_else(|| {
|
|
Python::with_gil(|py| -> PyResult<Option<(u64, PyObject)>> {
|
|
let obj: &PyAny = self.0.module.extract(py)?;
|
|
let mut sym_value: Option<(u64, PyObject)> = None;
|
|
let members: &PyDict = obj.getattr("__dict__").unwrap().downcast().unwrap();
|
|
for (key, val) in members {
|
|
let key: &str = key.extract()?;
|
|
if key == id.to_string() {
|
|
let id = self.0.helper.id_fn.call1(py, (val,))?.extract(py)?;
|
|
sym_value = Some((id, val.extract()?));
|
|
break;
|
|
}
|
|
}
|
|
if let Some((pyid, val)) = &sym_value {
|
|
self.0.id_to_pyval.write().insert(id, (*pyid, val.clone()));
|
|
}
|
|
Ok(sym_value)
|
|
})
|
|
.unwrap()
|
|
});
|
|
sym_value.map(|(id, v)| {
|
|
ValueEnum::Static(Arc::new(PythonValue {
|
|
id,
|
|
value: v,
|
|
store_obj: self.0.helper.store_obj.clone(),
|
|
resolver: self.0.clone(),
|
|
}))
|
|
})
|
|
}
|
|
|
|
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, HashSet<String>> {
|
|
{
|
|
let id_to_def = self.0.id_to_def.read();
|
|
id_to_def.get(&id).copied().ok_or_else(String::new)
|
|
}
|
|
.or_else(|_| {
|
|
let py_id = self
|
|
.0
|
|
.name_to_pyid
|
|
.get(&id)
|
|
.ok_or_else(|| HashSet::from([format!("Undefined identifier `{id}`")]))?;
|
|
let result = self.0.pyid_to_def.read().get(py_id).copied().ok_or_else(|| {
|
|
HashSet::from([format!(
|
|
"`{id}` is not registered with NAC3 (@nac3 decorator missing?)"
|
|
)])
|
|
})?;
|
|
self.0.id_to_def.write().insert(id, result);
|
|
Ok(result)
|
|
})
|
|
}
|
|
|
|
fn get_string_id(&self, s: &str) -> i32 {
|
|
let mut string_store = self.0.string_store.write();
|
|
if let Some(id) = string_store.get(s) {
|
|
*id
|
|
} else {
|
|
let id = i32::try_from(string_store.len()).unwrap();
|
|
string_store.insert(s.into(), id);
|
|
id
|
|
}
|
|
}
|
|
|
|
fn handle_deferred_eval(
|
|
&self,
|
|
unifier: &mut Unifier,
|
|
defs: &[Arc<RwLock<TopLevelDef>>],
|
|
primitives: &PrimitiveStore,
|
|
) -> Result<(), String> {
|
|
// we don't need a lock because this will only be run in a single thread
|
|
if self.0.deferred_eval_store.needs_defer.load(Relaxed) {
|
|
self.0.deferred_eval_store.needs_defer.store(false, Relaxed);
|
|
let store = self.0.deferred_eval_store.store.read();
|
|
Python::with_gil(|py| -> PyResult<Result<(), String>> {
|
|
for (variables, constraints, name) in store.iter() {
|
|
let constraints: &PyAny = constraints.as_ref(py);
|
|
for (i, var) in variables.iter().enumerate() {
|
|
if let Ok(constr) = constraints.get_item(i) {
|
|
match self.0.get_pyty_obj_type(py, constr, unifier, defs, primitives)? {
|
|
Ok((ty, _)) => {
|
|
if !unifier.is_concrete(ty, &[]) {
|
|
return Ok(Err(format!(
|
|
"the {}th constraint of TypeVar `{}` is not concrete",
|
|
i + 1,
|
|
name,
|
|
)));
|
|
}
|
|
unifier.unify(ty, *var).unwrap();
|
|
}
|
|
Err(err) => return Ok(Err(err)),
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Ok(Ok(()))
|
|
})
|
|
.unwrap()?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn get_exception_id(&self, tyid: usize) -> usize {
|
|
let exn_ids = self.0.exception_ids.read();
|
|
exn_ids.get(&tyid).copied().unwrap_or(0)
|
|
}
|
|
}
|