Compare commits

..

2 Commits

Author SHA1 Message Date
51f9f9c1e3 WIP 2024-08-16 17:20:12 +08:00
b1c5c2e1d4 [artiq] Fix RPC of ndarrays to host 2024-08-15 15:41:24 +08:00
12 changed files with 350 additions and 243 deletions

View File

@ -180,9 +180,7 @@
clippy clippy
pre-commit pre-commit
rustfmt rustfmt
rust-analyzer
]; ];
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
shellHook = shellHook =
'' ''
export DEMO_LINALG_STUB=${packages.x86_64-linux.demo-linalg-stub}/lib/liblinalg.a export DEMO_LINALG_STUB=${packages.x86_64-linux.demo-linalg-stub}/lib/liblinalg.a

View File

@ -1,9 +1,12 @@
use nac3core::{ use nac3core::{
codegen::{ codegen::{
classes::{ListValue, NDArrayValue, RangeValue, UntypedArrayLikeAccessor}, classes::{
ArrayLikeIndexer, ArrayLikeValue, ArraySliceValue, ListValue, NDArrayType,
NDArrayValue, RangeValue, UntypedArrayLikeAccessor,
},
expr::{destructure_range, gen_call}, expr::{destructure_range, gen_call},
irrt::call_ndarray_calc_size, irrt::call_ndarray_calc_size,
llvm_intrinsics::{call_int_smax, call_stackrestore, call_stacksave}, llvm_intrinsics::{call_int_smax, call_memcpy_generic, call_stackrestore, call_stacksave},
stmt::{gen_block, gen_for_callback_incrementing, gen_if_callback, gen_with}, stmt::{gen_block, gen_for_callback_incrementing, gen_if_callback, gen_with},
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}, },
@ -17,8 +20,8 @@ use nac3parser::ast::{Expr, ExprKind, Located, Stmt, StmtKind, StrRef};
use inkwell::{ use inkwell::{
context::Context, context::Context,
module::Linkage, module::Linkage,
types::IntType, types::{BasicType, IntType},
values::{BasicValueEnum, StructValue}, values::{BasicValueEnum, PointerValue, StructValue},
AddressSpace, IntPredicate, AddressSpace, IntPredicate,
}; };
@ -422,7 +425,10 @@ fn gen_rpc_tag(
} else { } else {
unreachable!() unreachable!()
}; };
assert!((0u64..=u64::from(u8::MAX)).contains(&ndarray_ndims)); assert!(
(0u64..=u64::from(u8::MAX)).contains(&ndarray_ndims),
"Only NDArrays of sizes between 0 and 255 can be RPCed"
);
buffer.push(b'a'); buffer.push(b'a');
buffer.push((ndarray_ndims & 0xFF) as u8); buffer.push((ndarray_ndims & 0xFF) as u8);
@ -434,6 +440,95 @@ fn gen_rpc_tag(
Ok(()) Ok(())
} }
/// Formats an RPC argument to conform to the expected format required by `send_value`.
///
/// See `artiq/firmware/libproto_artiq/rpc_proto.rs` for the expected format.
fn format_rpc_arg<'ctx>(
generator: &mut dyn CodeGenerator,
ctx: &mut CodeGenContext<'ctx, '_>,
(arg, arg_ty, arg_idx): (BasicValueEnum<'ctx>, Type, usize),
) -> PointerValue<'ctx> {
let llvm_i8 = ctx.ctx.i8_type();
let llvm_pi8 = llvm_i8.ptr_type(AddressSpace::default());
let arg_slot = match &*ctx.unifier.get_ty_immutable(arg_ty) {
TypeEnum::TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id() => {
// NAC3: NDArray = { usize, usize*, T* }
// libproto_artiq: NDArray = [data[..], dim_sz[..]]
let llvm_i1 = ctx.ctx.bool_type();
let llvm_usize = generator.get_size_type(ctx.ctx);
let (elem_ty, _) = unpack_ndarray_var_tys(&mut ctx.unifier, arg_ty);
let llvm_arg_ty =
NDArrayType::new(generator, ctx.ctx, ctx.get_llvm_type(generator, elem_ty));
let llvm_arg = NDArrayValue::from_ptr_val(arg.into_pointer_value(), llvm_usize, None);
let llvm_usize_sizeof = ctx
.builder
.build_int_truncate_or_bit_cast(llvm_arg_ty.size_type().size_of(), llvm_usize, "")
.unwrap();
let llvm_pdata_sizeof = ctx
.builder
.build_int_truncate_or_bit_cast(
llvm_arg_ty.element_type().ptr_type(AddressSpace::default()).size_of(),
llvm_usize,
"",
)
.unwrap();
let dims_buf_sz =
ctx.builder.build_int_mul(llvm_arg.load_ndims(ctx), llvm_usize_sizeof, "").unwrap();
let buffer_size =
ctx.builder.build_int_add(dims_buf_sz, llvm_pdata_sizeof, "").unwrap();
let buffer = ctx.builder.build_array_alloca(llvm_i8, buffer_size, "rpc.arg").unwrap();
let buffer = ArraySliceValue::from_ptr_val(buffer, buffer_size, Some("rpc.arg"));
let ppdata =
generator.gen_var_alloc(ctx, llvm_arg_ty.element_type(), None).unwrap();
ctx.builder.build_store(ppdata, llvm_arg.data().base_ptr(ctx, generator)).unwrap();
call_memcpy_generic(
ctx,
buffer.base_ptr(ctx, generator),
ppdata,
llvm_pdata_sizeof,
llvm_i1.const_zero(),
);
let pbuffer_dims_begin =
unsafe { buffer.ptr_offset_unchecked(ctx, generator, &llvm_pdata_sizeof, None) };
call_memcpy_generic(
ctx,
pbuffer_dims_begin,
llvm_arg.dim_sizes().base_ptr(ctx, generator),
dims_buf_sz,
llvm_i1.const_zero(),
);
buffer.base_ptr(ctx, generator)
}
_ => {
let arg_slot = generator
.gen_var_alloc(ctx, arg.get_type(), Some(&format!("rpc.arg{arg_idx}")))
.unwrap();
ctx.builder.build_store(arg_slot, arg).unwrap();
ctx.builder
.build_bitcast(arg_slot, llvm_pi8, "rpc.arg")
.map(BasicValueEnum::into_pointer_value)
.unwrap()
}
};
debug_assert_eq!(arg_slot.get_type(), llvm_pi8);
arg_slot
}
fn rpc_codegen_callback_fn<'ctx>( fn rpc_codegen_callback_fn<'ctx>(
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
obj: Option<(Type, ValueEnum<'ctx>)>, obj: Option<(Type, ValueEnum<'ctx>)>,
@ -441,10 +536,10 @@ fn rpc_codegen_callback_fn<'ctx>(
args: Vec<(Option<StrRef>, ValueEnum<'ctx>)>, args: Vec<(Option<StrRef>, ValueEnum<'ctx>)>,
generator: &mut dyn CodeGenerator, generator: &mut dyn CodeGenerator,
) -> Result<Option<BasicValueEnum<'ctx>>, String> { ) -> Result<Option<BasicValueEnum<'ctx>>, String> {
let ptr_type = ctx.ctx.i8_type().ptr_type(AddressSpace::default());
let size_type = generator.get_size_type(ctx.ctx);
let int8 = ctx.ctx.i8_type(); let int8 = ctx.ctx.i8_type();
let int32 = ctx.ctx.i32_type(); let int32 = ctx.ctx.i32_type();
let size_type = generator.get_size_type(ctx.ctx);
let ptr_type = int8.ptr_type(AddressSpace::default());
let tag_ptr_type = ctx.ctx.struct_type(&[ptr_type.into(), size_type.into()], false); let tag_ptr_type = ctx.ctx.struct_type(&[ptr_type.into(), size_type.into()], false);
let service_id = int32.const_int(fun.1 .0 as u64, false); let service_id = int32.const_int(fun.1 .0 as u64, false);
@ -517,22 +612,25 @@ fn rpc_codegen_callback_fn<'ctx>(
.0 .0
.args .args
.iter() .iter()
.map(|arg| mapping.remove(&arg.name).unwrap().to_basic_value_enum(ctx, generator, arg.ty)) .map(|arg| {
.collect::<Result<Vec<_>, _>>()?; mapping
.remove(&arg.name)
.unwrap()
.to_basic_value_enum(ctx, generator, arg.ty)
.map(|llvm_val| (llvm_val, arg.ty))
})
.collect::<Result<Vec<(_, _)>, _>>()?;
if let Some(obj) = obj { if let Some(obj) = obj {
if let ValueEnum::Static(obj) = obj.1 { if let ValueEnum::Static(obj_val) = obj.1 {
real_params.insert(0, obj.get_const_obj(ctx, generator)); real_params.insert(0, (obj_val.get_const_obj(ctx, generator), obj.0));
} else { } else {
// should be an error here... // should be an error here...
panic!("only host object is allowed"); panic!("only host object is allowed");
} }
} }
for (i, arg) in real_params.iter().enumerate() { for (i, (arg, arg_ty)) in real_params.iter().enumerate() {
let arg_slot = let arg_slot = format_rpc_arg(generator, ctx, (*arg, *arg_ty, i));
generator.gen_var_alloc(ctx, arg.get_type(), Some(&format!("rpc.arg{i}"))).unwrap();
ctx.builder.build_store(arg_slot, *arg).unwrap();
let arg_slot = ctx.builder.build_bitcast(arg_slot, ptr_type, "rpc.arg").unwrap();
let arg_ptr = unsafe { let arg_ptr = unsafe {
ctx.builder.build_gep( ctx.builder.build_gep(
args_ptr, args_ptr,

View File

@ -2982,23 +2982,8 @@ pub fn gen_expr<'ctx, G: CodeGenerator>(
} }
} }
ExprKind::Call { func, args, keywords } => { ExprKind::Call { func, args, keywords } => {
// Check if expr is override or not
let mut is_override = false;
if let Some(arg) = args.first() {
if let ExprKind::Name { id, .. } = arg.node {
if id == "self".into() {
is_override = true;
}
}
}
let mut args = args.clone();
if is_override {
args.remove(0);
}
let mut params = args let mut params = args
.iter() .iter()
.skip(if is_override { 1 } else { 0 })
.map(|arg| generator.gen_expr(ctx, arg)) .map(|arg| generator.gen_expr(ctx, arg))
.take_while(|expr| !matches!(expr, Ok(None))) .take_while(|expr| !matches!(expr, Ok(None)))
.map(|expr| Ok((None, expr?.unwrap())) as Result<_, String>) .map(|expr| Ok((None, expr?.unwrap())) as Result<_, String>)
@ -3040,24 +3025,24 @@ pub fn gen_expr<'ctx, G: CodeGenerator>(
let Some(val) = generator.gen_expr(ctx, value)? else { return Ok(None) }; let Some(val) = generator.gen_expr(ctx, value)? else { return Ok(None) };
// Handle Class Method calls // Handle Class Method calls
let id = if let TypeEnum::TObj { obj_id, .. } = // The attribute will be `DefinitionId` of the method if the call is to one of the parent methods
&*ctx.unifier.get_ty(value.custom.unwrap()) let func_id = attr.to_string().parse::<usize>();
{
let id = if let TypeEnum::TObj { obj_id, .. } = &*ctx.unifier.get_ty(value.custom.unwrap()) {
*obj_id *obj_id
} else { } else {
unreachable!() unreachable!()
}; };
let fun_id = {
// Use the `DefinitionID` from attribute if it is available
let fun_id = if func_id.is_ok() {
DefinitionId(func_id.unwrap())
} else {
let defs = ctx.top_level.definitions.read(); let defs = ctx.top_level.definitions.read();
let obj_def = defs.get(id.0).unwrap().read(); let obj_def = defs.get(id.0).unwrap().read();
let TopLevelDef::Class { methods, virtual_table, .. } = &*obj_def else { let TopLevelDef::Class { methods, .. } = &*obj_def else { unreachable!() };
unreachable!()
};
if is_override {
virtual_table.get(attr).unwrap().1
} else {
methods.iter().find(|method| method.0 == *attr).unwrap().2 methods.iter().find(|method| method.0 == *attr).unwrap().2
}
}; };
// directly generate code for option.unwrap // directly generate code for option.unwrap
// since it needs to return static value to optimize for kernel invariant // since it needs to return static value to optimize for kernel invariant

View File

@ -96,7 +96,6 @@ pub fn get_exn_constructor(
fields: exception_fields, fields: exception_fields,
attributes: Vec::default(), attributes: Vec::default(),
methods: vec![("__init__".into(), signature, DefinitionId(cons_id))], methods: vec![("__init__".into(), signature, DefinitionId(cons_id))],
virtual_table: HashMap::default(),
ancestors: vec![ ancestors: vec![
TypeAnnotation::CustomClass { id: DefinitionId(class_id), params: Vec::default() }, TypeAnnotation::CustomClass { id: DefinitionId(class_id), params: Vec::default() },
TypeAnnotation::CustomClass { id: PrimDef::Exception.id(), params: Vec::default() }, TypeAnnotation::CustomClass { id: PrimDef::Exception.id(), params: Vec::default() },
@ -690,7 +689,6 @@ impl<'a> BuiltinBuilder<'a> {
fields, fields,
attributes: Vec::default(), attributes: Vec::default(),
methods: vec![("__init__".into(), ctor_signature, PrimDef::FunRangeInit.id())], methods: vec![("__init__".into(), ctor_signature, PrimDef::FunRangeInit.id())],
virtual_table: HashMap::default(),
ancestors: Vec::default(), ancestors: Vec::default(),
constructor: Some(ctor_signature), constructor: Some(ctor_signature),
resolver: None, resolver: None,
@ -823,7 +821,6 @@ impl<'a> BuiltinBuilder<'a> {
fields: make_exception_fields(int32, int64, str), fields: make_exception_fields(int32, int64, str),
attributes: Vec::default(), attributes: Vec::default(),
methods: Vec::default(), methods: Vec::default(),
virtual_table: HashMap::default(),
ancestors: vec![], ancestors: vec![],
constructor: None, constructor: None,
resolver: None, resolver: None,
@ -858,7 +855,6 @@ impl<'a> BuiltinBuilder<'a> {
Self::create_method(PrimDef::FunOptionIsNone, self.is_some_ty.0), Self::create_method(PrimDef::FunOptionIsNone, self.is_some_ty.0),
Self::create_method(PrimDef::FunOptionUnwrap, self.unwrap_ty.0), Self::create_method(PrimDef::FunOptionUnwrap, self.unwrap_ty.0),
], ],
virtual_table: HashMap::default(),
ancestors: vec![TypeAnnotation::CustomClass { ancestors: vec![TypeAnnotation::CustomClass {
id: prim.id(), id: prim.id(),
params: Vec::default(), params: Vec::default(),
@ -966,7 +962,6 @@ impl<'a> BuiltinBuilder<'a> {
fields: Vec::default(), fields: Vec::default(),
attributes: Vec::default(), attributes: Vec::default(),
methods: Vec::default(), methods: Vec::default(),
virtual_table: HashMap::default(),
ancestors: Vec::default(), ancestors: Vec::default(),
constructor: None, constructor: None,
resolver: None, resolver: None,
@ -995,7 +990,6 @@ impl<'a> BuiltinBuilder<'a> {
Self::create_method(PrimDef::FunNDArrayCopy, self.ndarray_copy_ty.0), Self::create_method(PrimDef::FunNDArrayCopy, self.ndarray_copy_ty.0),
Self::create_method(PrimDef::FunNDArrayFill, self.ndarray_fill_ty.0), Self::create_method(PrimDef::FunNDArrayFill, self.ndarray_fill_ty.0),
], ],
virtual_table: HashMap::default(),
ancestors: Vec::default(), ancestors: Vec::default(),
constructor: None, constructor: None,
resolver: None, resolver: None,

View File

@ -23,7 +23,7 @@ impl Default for ComposerConfig {
} }
} }
type DefAst = (Arc<RwLock<TopLevelDef>>, Option<Stmt<()>>); pub type DefAst = (Arc<RwLock<TopLevelDef>>, Option<Stmt<()>>);
pub struct TopLevelComposer { pub struct TopLevelComposer {
// list of top level definitions, same as top level context // list of top level definitions, same as top level context
pub definition_ast_list: Vec<DefAst>, pub definition_ast_list: Vec<DefAst>,
@ -1528,9 +1528,9 @@ impl TopLevelComposer {
fn analyze_single_class_ancestors( fn analyze_single_class_ancestors(
class_def: &mut TopLevelDef, class_def: &mut TopLevelDef,
temp_def_list: &[Arc<RwLock<TopLevelDef>>], temp_def_list: &[Arc<RwLock<TopLevelDef>>],
_unifier: &mut Unifier, unifier: &mut Unifier,
_primitives: &PrimitiveStore, _primitives: &PrimitiveStore,
_type_var_to_concrete_def: &mut HashMap<Type, TypeAnnotation>, type_var_to_concrete_def: &mut HashMap<Type, TypeAnnotation>,
) -> Result<(), HashSet<String>> { ) -> Result<(), HashSet<String>> {
let TopLevelDef::Class { let TopLevelDef::Class {
object_id, object_id,
@ -1538,7 +1538,6 @@ impl TopLevelComposer {
fields, fields,
attributes, attributes,
methods, methods,
virtual_table,
resolver, resolver,
type_vars, type_vars,
.. ..
@ -1552,19 +1551,9 @@ impl TopLevelComposer {
class_fields_def, class_fields_def,
class_attribute_def, class_attribute_def,
class_methods_def, class_methods_def,
class_virtual_table,
_class_type_vars_def, _class_type_vars_def,
_class_resolver, _class_resolver,
) = ( ) = (*object_id, ancestors, fields, attributes, methods, type_vars, resolver);
*object_id,
ancestors,
fields,
attributes,
methods,
virtual_table,
type_vars,
resolver,
);
// since when this function is called, the ancestors of the direct parent // since when this function is called, the ancestors of the direct parent
// are supposed to be already handled, so we only need to deal with the direct parent // are supposed to be already handled, so we only need to deal with the direct parent
@ -1575,88 +1564,51 @@ impl TopLevelComposer {
let base = temp_def_list.get(id.0).unwrap(); let base = temp_def_list.get(id.0).unwrap();
let base = base.read(); let base = base.read();
let TopLevelDef::Class { methods, virtual_table, fields, attributes, .. } = &*base else { let TopLevelDef::Class { methods, fields, attributes, .. } = &*base else {
unreachable!("must be top level class def") unreachable!("must be top level class def")
}; };
// handle methods override // handle methods override
// since we need to maintain the order, create a new list // since we need to maintain the order, create a new list
// handle methods override
// Since we are following python and its lax syntax, signature is ignored in overriding
// Mark the overrided methods and add them to the child overrides
let mut new_child_methods: Vec<(StrRef, Type, DefinitionId)> = Vec::new(); let mut new_child_methods: Vec<(StrRef, Type, DefinitionId)> = Vec::new();
let mut new_child_virtual_table: HashMap<StrRef, (Type, DefinitionId)> =
virtual_table.clone();
let mut is_override: HashSet<StrRef> = HashSet::new(); let mut is_override: HashSet<StrRef> = HashSet::new();
for (anc_method_name, anc_method_ty, anc_method_def_id) in methods { for (anc_method_name, anc_method_ty, anc_method_def_id) in methods {
// find if there is a method with same name in the child class
let mut to_be_added = (*anc_method_name, *anc_method_ty, *anc_method_def_id); let mut to_be_added = (*anc_method_name, *anc_method_ty, *anc_method_def_id);
for class_method in &*class_methods_def { for (class_method_name, class_method_ty, class_method_defid) in &*class_methods_def {
if class_method.0 == *anc_method_name { if class_method_name == anc_method_name {
to_be_added = *class_method; // ignore and handle self
// Add to virtual table // if is __init__ method, no need to check return type
new_child_virtual_table let ok = class_method_name == &"__init__".into()
.insert(class_method.0, (class_method.1, class_method.2)); || Self::check_overload_function_type(
is_override.insert(class_method.0); *class_method_ty,
*anc_method_ty,
unifier,
type_var_to_concrete_def,
);
if !ok {
return Err(HashSet::from([format!(
"method {class_method_name} has same name as ancestors' method, but incompatible type"),
]));
}
// mark it as added
is_override.insert(*class_method_name);
to_be_added = (*class_method_name, *class_method_ty, *class_method_defid);
break; break;
} }
} }
new_child_methods.push(to_be_added); new_child_methods.push(to_be_added);
} }
for class_method in &*class_methods_def { // add those that are not overriding method to the new_child_methods
if !is_override.contains(&class_method.0) { for (class_method_name, class_method_ty, class_method_defid) in &*class_methods_def {
new_child_methods.push(*class_method); if !is_override.contains(class_method_name) {
new_child_methods.push((*class_method_name, *class_method_ty, *class_method_defid));
} }
} }
/*
Call => class method
super() or class_name A.f1() => method, virtual_tables
*/
// for (anc_method_name, anc_method_ty, anc_method_def_id) in methods {
// // find if there is a method with same name in the child class
// let mut to_be_added = (*anc_method_name, *anc_method_ty, *anc_method_def_id);
// for (class_method_name, class_method_ty, class_method_defid) in &*class_methods_def {
// if class_method_name == anc_method_name {
// // ignore and handle self
// // if is __init__ method, no need to check return type
// let ok = class_method_name == &"__init__".into()
// || Self::check_overload_function_type(
// *class_method_ty,
// *anc_method_ty,
// unifier,
// type_var_to_concrete_def,
// );
// if !ok {
// return Err(HashSet::from([format!(
// "method {class_method_name} has same name as ancestors' method, but incompatible type"),
// ]));
// }
// // mark it as added
// is_override.insert(*class_method_name);
// to_be_added = (*class_method_name, *class_method_ty, *class_method_defid);
// break;
// }
// }
// new_child_methods.push(to_be_added);
// }
// // add those that are not overriding method to the new_child_methods
// for (class_method_name, class_method_ty, class_method_defid) in &*class_methods_def {
// if !is_override.contains(class_method_name) {
// new_child_methods.push((*class_method_name, *class_method_ty, *class_method_defid));
// }
// }
// use the new_child_methods to replace all the elements in `class_methods_def` // use the new_child_methods to replace all the elements in `class_methods_def`
class_methods_def.clear(); class_methods_def.clear();
class_methods_def.extend(new_child_methods); class_methods_def.extend(new_child_methods);
class_virtual_table.clear();
class_virtual_table.extend(new_child_virtual_table);
// handle class fields // handle class fields
let mut new_child_fields: Vec<(StrRef, Type, bool)> = Vec::new(); let mut new_child_fields: Vec<(StrRef, Type, bool)> = Vec::new();
// let mut is_override: HashSet<_> = HashSet::new(); // let mut is_override: HashSet<_> = HashSet::new();
@ -1870,7 +1822,15 @@ impl TopLevelComposer {
if *name != init_str_id { if *name != init_str_id {
unreachable!("must be init function here") unreachable!("must be init function here")
} }
let all_inited = Self::get_all_assigned_field(body.as_slice())?; // Since AST stores class names without prepending `__module__.`, we split the name for search purposes
let class_name_only = class_name.to_string();
let (_, class_name_only) = class_name_only.split_once('.').unwrap();
let all_inited = Self::get_all_assigned_field(
class_name_only.into(),
definition_ast_list,
body.as_slice(),
)?;
for (f, _, _) in fields { for (f, _, _) in fields {
if !all_inited.contains(f) { if !all_inited.contains(f) {
return Err(HashSet::from([ return Err(HashSet::from([

View File

@ -3,6 +3,7 @@ use std::convert::TryInto;
use crate::symbol_resolver::SymbolValue; use crate::symbol_resolver::SymbolValue;
use crate::toplevel::numpy::unpack_ndarray_var_tys; use crate::toplevel::numpy::unpack_ndarray_var_tys;
use crate::typecheck::typedef::{into_var_map, iter_type_vars, Mapping, TypeVarId, VarMap}; use crate::typecheck::typedef::{into_var_map, iter_type_vars, Mapping, TypeVarId, VarMap};
use ast::ExprKind;
use nac3parser::ast::{Constant, Location}; use nac3parser::ast::{Constant, Location};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use strum_macros::EnumIter; use strum_macros::EnumIter;
@ -559,7 +560,6 @@ impl TopLevelComposer {
fields: Vec::default(), fields: Vec::default(),
attributes: Vec::default(), attributes: Vec::default(),
methods: Vec::default(), methods: Vec::default(),
virtual_table: HashMap::default(),
ancestors: Vec::default(), ancestors: Vec::default(),
constructor, constructor,
resolver, resolver,
@ -734,7 +734,11 @@ impl TopLevelComposer {
) )
} }
pub fn get_all_assigned_field(stmts: &[Stmt<()>]) -> Result<HashSet<StrRef>, HashSet<String>> { pub fn get_all_assigned_field(
class_name: StrRef,
ast: &Vec<DefAst>,
stmts: &[Stmt<()>],
) -> Result<HashSet<StrRef>, HashSet<String>> {
let mut result = HashSet::new(); let mut result = HashSet::new();
for s in stmts { for s in stmts {
match &s.node { match &s.node {
@ -770,30 +774,106 @@ impl TopLevelComposer {
// TODO: do not check for For and While? // TODO: do not check for For and While?
ast::StmtKind::For { body, orelse, .. } ast::StmtKind::For { body, orelse, .. }
| ast::StmtKind::While { body, orelse, .. } => { | ast::StmtKind::While { body, orelse, .. } => {
result.extend(Self::get_all_assigned_field(body.as_slice())?); result.extend(Self::get_all_assigned_field(class_name, ast, body.as_slice())?);
result.extend(Self::get_all_assigned_field(orelse.as_slice())?); result.extend(Self::get_all_assigned_field(
class_name,
ast,
orelse.as_slice(),
)?);
} }
ast::StmtKind::If { body, orelse, .. } => { ast::StmtKind::If { body, orelse, .. } => {
let inited_for_sure = Self::get_all_assigned_field(body.as_slice())? let inited_for_sure =
.intersection(&Self::get_all_assigned_field(orelse.as_slice())?) Self::get_all_assigned_field(class_name, ast, body.as_slice())?
.intersection(&Self::get_all_assigned_field(
class_name,
ast,
orelse.as_slice(),
)?)
.copied() .copied()
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
result.extend(inited_for_sure); result.extend(inited_for_sure);
} }
ast::StmtKind::Try { body, orelse, finalbody, .. } => { ast::StmtKind::Try { body, orelse, finalbody, .. } => {
let inited_for_sure = Self::get_all_assigned_field(body.as_slice())? let inited_for_sure =
.intersection(&Self::get_all_assigned_field(orelse.as_slice())?) Self::get_all_assigned_field(class_name, ast, body.as_slice())?
.intersection(&Self::get_all_assigned_field(
class_name,
ast,
orelse.as_slice(),
)?)
.copied() .copied()
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
result.extend(inited_for_sure); result.extend(inited_for_sure);
result.extend(Self::get_all_assigned_field(finalbody.as_slice())?); result.extend(Self::get_all_assigned_field(
class_name,
ast,
finalbody.as_slice(),
)?);
} }
ast::StmtKind::With { body, .. } => { ast::StmtKind::With { body, .. } => {
result.extend(Self::get_all_assigned_field(body.as_slice())?); result.extend(Self::get_all_assigned_field(class_name, ast, body.as_slice())?);
}
// Variables Initiated in function calls
ast::StmtKind::Expr { value, .. } => {
let ExprKind::Call { func, .. } = &value.node else {
continue;
};
let ExprKind::Attribute { value, attr, .. } = &func.node else {
continue;
};
let ExprKind::Name { id, .. } = &value.node else {
continue;
};
// Need to conside the two cases:
// Case 1) Call to class function i.e. id = `self`
// Case 2) Call to class ancestor function i.e. id = ancestor_name
// We leave checking whether function in case 2 belonged to class ancestor or not to type checker
//
// According to current handling of `self`, function definition are fixed and do not change regardless
// of which object is passed as `self` i.e. virtual polymorphism is not supported
// Therefore, we change class name for case 2 to reflect behavior of our compiler
let new_class_name = if *id == "self".into() { class_name } else { *id };
let method_body = ast.iter().find_map(|def| {
let Some(ast::Located {
node: ast::StmtKind::ClassDef { name, body, .. },
..
}) = &def.1
else {
return None;
};
if *name == new_class_name {
body.iter().find_map(|m| {
let ast::StmtKind::FunctionDef { name, body, .. } = &m.node else {
return None;
};
if *name == *attr {
return Some(body.clone());
}
None
})
} else {
None
}
});
// If method body is none then method does not exist
if let Some(method_body) = method_body {
result.extend(Self::get_all_assigned_field(
new_class_name,
ast,
method_body.as_slice(),
)?);
} else {
return Err(HashSet::from([format!(
"{}.{} not found in class {new_class_name} at {}",
*id, *attr, value.location
)]));
}
} }
ast::StmtKind::Pass { .. } ast::StmtKind::Pass { .. }
| ast::StmtKind::Assert { .. } | ast::StmtKind::Assert { .. }
| ast::StmtKind::Expr { .. } => {} | ast::StmtKind::AnnAssign { .. } => {}
_ => { _ => {
unimplemented!() unimplemented!()

View File

@ -109,8 +109,6 @@ pub enum TopLevelDef {
attributes: Vec<(StrRef, Type, ast::Constant)>, attributes: Vec<(StrRef, Type, ast::Constant)>,
/// Class methods, pointing to the corresponding function definition. /// Class methods, pointing to the corresponding function definition.
methods: Vec<(StrRef, Type, DefinitionId)>, methods: Vec<(StrRef, Type, DefinitionId)>,
/// Overridden class methods
virtual_table: HashMap<StrRef, (Type, DefinitionId)>,
/// Ancestor classes, including itself. /// Ancestor classes, including itself.
ancestors: Vec<TypeAnnotation>, ancestors: Vec<TypeAnnotation>,
/// Symbol resolver of the module defined the class; [None] if it is built-in type. /// Symbol resolver of the module defined the class; [None] if it is built-in type.

View File

@ -103,6 +103,7 @@ pub struct Inferencer<'a> {
} }
type InferenceError = HashSet<String>; type InferenceError = HashSet<String>;
type OverrideResult = Result<Option<ast::Expr<Option<Type>>>, InferenceError>;
struct NaiveFolder(); struct NaiveFolder();
impl Fold<()> for NaiveFolder { impl Fold<()> for NaiveFolder {
@ -1673,18 +1674,14 @@ impl<'a> Inferencer<'a> {
Ok(None) Ok(None)
} }
fn try_overriding( /// Checks whether a class method is calling parent function
&mut self, /// Returns [`None`] if its not a call to parent method, otherwise
func: &ast::Expr<()>, /// returns a new `func` with class name replaced by `self` and method resolved to its `DefinitionID`
args: &mut [ast::Expr<()>], ///
) -> Result<Option<(ast::Expr<()>, Type)>, InferenceError> { /// e.g. A.f1(self, ...) returns Some(self.DefintionID(f1))
// Allow Overriding fn check_overriding(&mut self, func: &ast::Expr<()>, args: &[ast::Expr<()>]) -> OverrideResult {
// `self` must be first argument for call to parent method
// Must have self as first input if let Some(Located { node: ExprKind::Name { id, .. }, .. }) = &args.first() {
if args.is_empty() {
return Ok(None);
}
if let Located { node: ExprKind::Name { id, .. }, .. } = &args[0] {
if *id != "self".into() { if *id != "self".into() {
return Ok(None); return Ok(None);
} }
@ -1701,9 +1698,9 @@ impl<'a> Inferencer<'a> {
let ExprKind::Name { id: class_name, ctx: class_ctx } = &value.node else { let ExprKind::Name { id: class_name, ctx: class_ctx } = &value.node else {
return Ok(None); return Ok(None);
}; };
// Do not Remove self from args (will move it class name instead after activating necessary flags)
let zelf = &self.fold_expr(args[0].clone())?; let zelf = &self.fold_expr(args[0].clone())?;
// Check whether the method belongs to class ancestors
let def_id = self.unifier.get_ty(zelf.custom.unwrap()); let def_id = self.unifier.get_ty(zelf.custom.unwrap());
let TypeEnum::TObj { obj_id, .. } = def_id.as_ref() else { unreachable!() }; let TypeEnum::TObj { obj_id, .. } = def_id.as_ref() else { unreachable!() };
let defs = self.top_level.definitions.read(); let defs = self.top_level.definitions.read();
@ -1714,6 +1711,7 @@ impl<'a> Inferencer<'a> {
let TopLevelDef::Class { name, methods, .. } = &*defs[id.0].read() else { let TopLevelDef::Class { name, methods, .. } = &*defs[id.0].read() else {
unreachable!() unreachable!()
}; };
// Class names are stored as `__module__.class`
let name = name.to_string(); let name = name.to_string();
let (_, name) = name.split_once('.').unwrap(); let (_, name) = name.split_once('.').unwrap();
if name == class_name.to_string() { if name == class_name.to_string() {
@ -1732,25 +1730,28 @@ impl<'a> Inferencer<'a> {
} }
}; };
if let Some(f) = res { match res {
if let TopLevelDef::Class { virtual_table, .. } = &mut *defs[obj_id.0].write() { Some(r) => {
virtual_table.insert(f.0, (f.1, f.2));
}
} else {
return report_error(
format!("No such function found in parent class {}", method_name).as_str(),
*location,
);
}
// let Located { node: ExprKind::Attribute { value, attr: method_name, .. }, location, .. } = func
// Change the class name to self to refer to correct part of code
// let new_func = Located { node: ExprKind::Attribute { value, attr: method_name, ctx: () }, location, custom}
let mut new_func = func.clone(); let mut new_func = func.clone();
let mut new_value = value.clone(); let mut new_value = value.clone();
new_value.node = ExprKind::Name { id: "self".into(), ctx: *class_ctx }; new_value.node = ExprKind::Name { id: "self".into(), ctx: *class_ctx };
new_func.node = ExprKind::Attribute { value: new_value, attr: *method_name, ctx: *ctx }; new_func.node =
ExprKind::Attribute { value: new_value.clone(), attr: *method_name, ctx: *ctx };
Ok(Some((new_func, res.unwrap().1))) let mut new_func = self.fold_expr(new_func)?;
let ExprKind::Attribute { value, .. } = new_func.node else { unreachable!() };
new_func.node = ExprKind::Attribute { value, attr: r.2.0.to_string().into(), ctx: *ctx };
new_func.custom = Some(r.1);
Ok(Some(new_func))
}
None => report_error(
format!("Method {class_name}.{method_name} not found in ancestor list").as_str(),
*location,
),
}
} }
fn fold_call( fn fold_call(
@ -1766,32 +1767,26 @@ impl<'a> Inferencer<'a> {
return Ok(spec_call_func); return Ok(spec_call_func);
} }
let mut first_arg = None; // Check for call to parent method
let mut is_override = false; let override_res = self.check_overriding(&func, &args)?;
let mut func_sign_key = None; let is_override = override_res.is_some();
let override_res = self.try_overriding(&func, &mut args)?; let func = if is_override { override_res.unwrap() } else { self.fold_expr(func)? };
let func = match override_res { let func = Box::new(func);
Some(res) => {
is_override = true;
func_sign_key = Some(res.1);
res.0
}
None => func,
};
let func = Box::new(self.fold_expr(func)?);
let mut args = let mut args =
args.into_iter().map(|v| self.fold_expr(v)).collect::<Result<Vec<_>, _>>()?; args.into_iter().map(|v| self.fold_expr(v)).collect::<Result<Vec<_>, _>>()?;
// TODO: Handle passing of self to functions to allow runtime lookup of functions to be called
// Currently removing `self` and using compile time function definitions
if is_override { if is_override {
first_arg = Some(args.remove(0)); args.remove(0);
} }
let keywords = keywords let keywords = keywords
.into_iter() .into_iter()
.map(|v| fold::fold_keyword(self, v)) .map(|v| fold::fold_keyword(self, v))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let func_key = if is_override { func_sign_key.unwrap() } else { func.custom.unwrap() }; if let TypeEnum::TFunc(sign) = &*self.unifier.get_ty(func.custom.unwrap()) {
if let TypeEnum::TFunc(sign) = &*self.unifier.get_ty(func_key) {
if sign.vars.is_empty() { if sign.vars.is_empty() {
let call = Call { let call = Call {
posargs: args.iter().map(|v| v.custom.unwrap()).collect(), posargs: args.iter().map(|v| v.custom.unwrap()).collect(),
@ -1804,15 +1799,9 @@ impl<'a> Inferencer<'a> {
loc: Some(location), loc: Some(location),
operator_info: None, operator_info: None,
}; };
self.unifier.unify_call(&call, func_key, sign).map_err(|e| { self.unifier.unify_call(&call, func.custom.unwrap(), sign).map_err(|e| {
HashSet::from([e.at(Some(location)).to_display(self.unifier).to_string()]) HashSet::from([e.at(Some(location)).to_display(self.unifier).to_string()])
})?; })?;
// First parameter is self to indicate override
if let Some(mut arg) = first_arg {
arg.node = ExprKind::Name { id: "self".into(), ctx: ExprContext::Load };
args.insert(0, arg);
}
return Ok(Located { return Ok(Located {
location, location,
custom: Some(sign.ret), custom: Some(sign.ret),
@ -1836,10 +1825,7 @@ impl<'a> Inferencer<'a> {
self.calls.insert(location.into(), call); self.calls.insert(location.into(), call);
let call = self.unifier.add_ty(TypeEnum::TCall(vec![call])); let call = self.unifier.add_ty(TypeEnum::TCall(vec![call]));
self.unify(func.custom.unwrap(), call, &func.location)?; self.unify(func.custom.unwrap(), call, &func.location)?;
println!("Here");
for k in keywords.iter() {
println!("keyword {}", k.node.value.node.name());
}
Ok(Located { location, custom: Some(ret), node: ExprKind::Call { func, args, keywords } }) Ok(Located { location, custom: Some(ret), node: ExprKind::Call { func, args, keywords } })
} }

View File

@ -328,7 +328,6 @@ impl TestEnvironment {
fields: Vec::default(), fields: Vec::default(),
attributes: Vec::default(), attributes: Vec::default(),
methods: Vec::default(), methods: Vec::default(),
virtual_table: HashMap::default(),
ancestors: Vec::default(), ancestors: Vec::default(),
resolver: None, resolver: None,
constructor: None, constructor: None,
@ -373,7 +372,6 @@ impl TestEnvironment {
fields: [("a".into(), tvar.ty, true)].into(), fields: [("a".into(), tvar.ty, true)].into(),
attributes: Vec::default(), attributes: Vec::default(),
methods: Vec::default(), methods: Vec::default(),
virtual_table: HashMap::default(),
ancestors: Vec::default(), ancestors: Vec::default(),
resolver: None, resolver: None,
constructor: None, constructor: None,
@ -409,7 +407,6 @@ impl TestEnvironment {
fields: [("a".into(), int32, true), ("b".into(), fun, true)].into(), fields: [("a".into(), int32, true), ("b".into(), fun, true)].into(),
attributes: Vec::default(), attributes: Vec::default(),
methods: Vec::default(), methods: Vec::default(),
virtual_table: HashMap::default(),
ancestors: Vec::default(), ancestors: Vec::default(),
resolver: None, resolver: None,
constructor: None, constructor: None,
@ -439,7 +436,6 @@ impl TestEnvironment {
fields: [("a".into(), bool, true), ("b".into(), fun, false)].into(), fields: [("a".into(), bool, true), ("b".into(), fun, false)].into(),
attributes: Vec::default(), attributes: Vec::default(),
methods: Vec::default(), methods: Vec::default(),
virtual_table: HashMap::default(),
ancestors: Vec::default(), ancestors: Vec::default(),
resolver: None, resolver: None,
constructor: None, constructor: None,

View File

@ -1,3 +0,0 @@
12
12
17

View File

@ -1,3 +0,0 @@
12
12
15

View File

@ -7,43 +7,61 @@ def output_int32(x: int32):
class A: class A:
a: int32 a: int32
def __init__(self, param_a: int32): def __init__(self, a: int32):
self.a = param_a self.a = a
def f1(self): def output_all_fields(self):
output_int32(12) output_int32(self.a)
def set_a(self, a: int32):
self.a = a
class B(A): class B(A):
b: int32 b: int32
def __init__(self, param_a: int32, param_b: int32): def __init__(self, b: int32):
self.a = param_a A.__init__(self, b + 1)
self.b = param_b self.set_b(b)
def f1(self): def output_parent_fields(self):
output_int32(15) A.output_all_fields(self)
def f2(self): def output_all_fields(self):
A.f1(self) A.output_all_fields(self)
self.f1() output_int32(self.b)
class C(B): def set_b(self, b: int32):
def __init__(self, a: int32, b: int32):
self.a = a
self.b = b self.b = b
def f1(self): class C(B):
output_int32(17) c: int32
def f3(self): def __init__(self, c: int32):
B.f2(self) B.__init__(self, c + 1)
self.c = c
def f4(self): def output_parent_fields(self):
A.f1(self) B.output_all_fields(self)
def output_all_fields(self):
B.output_all_fields(self)
output_int32(self.c)
def set_c(self, c: int32):
self.c = c
def run() -> int32: def run() -> int32:
c = B(1, 2) ccc = C(10)
c.f2() ccc.output_all_fields()
ccc.set_a(1)
ccc.set_b(2)
ccc.set_c(3)
ccc.output_all_fields()
bbb = B(10)
bbb.set_a(9)
bbb.set_b(8)
bbb.output_all_fields()
ccc.output_all_fields()
return 0 return 0