diff --git a/nac3artiq/src/lib.rs b/nac3artiq/src/lib.rs index 925e49a9..8543b950 100644 --- a/nac3artiq/src/lib.rs +++ b/nac3artiq/src/lib.rs @@ -8,12 +8,12 @@ use inkwell::{ targets::*, OptimizationLevel, }; -use pyo3::prelude::*; -use pyo3::{exceptions, types::PyList, types::PySet, types::PyBytes}; use nac3parser::{ ast::{self, StrRef}, parser::{self, parse_program}, }; +use pyo3::prelude::*; +use pyo3::{exceptions, types::PyBytes, types::PyList, types::PySet}; use parking_lot::{Mutex, RwLock}; @@ -27,7 +27,10 @@ use nac3core::{ use tempfile::{self, TempDir}; -use crate::{codegen::ArtiqCodeGenerator, symbol_resolver::Resolver}; +use crate::{ + codegen::ArtiqCodeGenerator, + symbol_resolver::{InnerResolver, PythonHelper, Resolver}, +}; mod codegen; mod symbol_resolver; @@ -73,33 +76,45 @@ struct Nac3 { } impl Nac3 { - fn register_module(&mut self, module: PyObject, registered_class_ids: &HashSet) -> PyResult<()> { + fn register_module( + &mut self, + module: PyObject, + registered_class_ids: &HashSet, + ) -> PyResult<()> { let mut name_to_pyid: HashMap = HashMap::new(); - let (module_name, source_file) = Python::with_gil(|py| -> PyResult<(String, String)> { - let module: &PyAny = module.extract(py)?; - let builtins = PyModule::import(py, "builtins")?; - let id_fn = builtins.getattr("id")?; - let members: &PyList = PyModule::import(py, "inspect")? - .getattr("getmembers")? - .call1((module,))? - .cast_as()?; - for member in members.iter() { - let key: &str = member.get_item(0)?.extract()?; - let val = id_fn.call1((member.get_item(1)?,))?.extract()?; - name_to_pyid.insert(key.into(), val); - } - Ok(( - module.getattr("__name__")?.extract()?, - module.getattr("__file__")?.extract()?, - )) - })?; + let (module_name, source_file, helper) = + Python::with_gil(|py| -> PyResult<(String, String, PythonHelper)> { + let module: &PyAny = module.extract(py)?; + let builtins = PyModule::import(py, "builtins")?; + let id_fn = builtins.getattr("id")?; + let members: &PyList = PyModule::import(py, "inspect")? + .getattr("getmembers")? + .call1((module,))? + .cast_as()?; + for member in members.iter() { + let key: &str = member.get_item(0)?.extract()?; + let val = id_fn.call1((member.get_item(1)?,))?.extract()?; + name_to_pyid.insert(key.into(), val); + } + let helper = PythonHelper { + id_fn: builtins.getattr("id").unwrap().to_object(py), + len_fn: builtins.getattr("len").unwrap().to_object(py), + type_fn: builtins.getattr("type").unwrap().to_object(py), + }; + Ok(( + module.getattr("__name__")?.extract()?, + module.getattr("__file__")?.extract()?, + helper, + )) + })?; let source = fs::read_to_string(source_file).map_err(|e| { exceptions::PyIOError::new_err(format!("failed to read input file: {}", e)) })?; let parser_result = parser::parse_program(&source) .map_err(|e| exceptions::PySyntaxError::new_err(format!("parse error: {}", e)))?; - let resolver = Arc::new(Resolver { + + let resolver = Arc::new(Resolver(Arc::new(InnerResolver { id_to_type: self.builtins_ty.clone().into(), id_to_def: self.builtins_def.clone().into(), pyid_to_def: self.pyid_to_def.clone(), @@ -109,7 +124,8 @@ impl Nac3 { class_names: Default::default(), name_to_pyid: name_to_pyid.clone(), module: module.clone(), - }) as Arc; + helper, + }))) as Arc; let mut name_to_def = HashMap::new(); let mut name_to_type = HashMap::new(); @@ -140,10 +156,11 @@ impl Nac3 { let base_obj = module.getattr(py, id.to_string())?; let base_id = id_fn.call1((base_obj,))?.extract()?; Ok(registered_class_ids.contains(&base_id)) - }, - _ => Ok(true) + } + _ => Ok(true), } - }).unwrap() + }) + .unwrap() }); body.retain(|stmt| { if let ast::StmtKind::FunctionDef { @@ -306,7 +323,11 @@ impl Nac3 { }; let working_directory = tempfile::Builder::new().prefix("nac3-").tempdir().unwrap(); - fs::write(working_directory.path().join("kernel.ld"), include_bytes!("kernel.ld")).unwrap(); + fs::write( + working_directory.path().join("kernel.ld"), + include_bytes!("kernel.ld"), + ) + .unwrap(); Ok(Nac3 { isa, @@ -320,29 +341,30 @@ impl Nac3 { pyid_to_def: Default::default(), pyid_to_type: Default::default(), global_value_ids: Default::default(), - working_directory + working_directory, }) } fn analyze(&mut self, functions: &PySet, classes: &PySet) -> PyResult<()> { - let (modules, class_ids) = Python::with_gil(|py| -> PyResult<(HashMap, HashSet)> { - let mut modules: HashMap = HashMap::new(); - let mut class_ids: HashSet = HashSet::new(); + let (modules, class_ids) = + Python::with_gil(|py| -> PyResult<(HashMap, HashSet)> { + let mut modules: HashMap = HashMap::new(); + let mut class_ids: HashSet = HashSet::new(); - let id_fn = PyModule::import(py, "builtins")?.getattr("id")?; - let getmodule_fn = PyModule::import(py, "inspect")?.getattr("getmodule")?; + let id_fn = PyModule::import(py, "builtins")?.getattr("id")?; + let getmodule_fn = PyModule::import(py, "inspect")?.getattr("getmodule")?; - for function in functions.iter() { - let module = getmodule_fn.call1((function,))?.extract()?; - modules.insert(id_fn.call1((&module,))?.extract()?, module); - } - for class in classes.iter() { - let module = getmodule_fn.call1((class,))?.extract()?; - modules.insert(id_fn.call1((&module,))?.extract()?, module); - class_ids.insert(id_fn.call1((class,))?.extract()?); - } - Ok((modules, class_ids)) - })?; + for function in functions.iter() { + let module = getmodule_fn.call1((function,))?.extract()?; + modules.insert(id_fn.call1((&module,))?.extract()?, module); + } + for class in classes.iter() { + let module = getmodule_fn.call1((class,))?.extract()?; + modules.insert(id_fn.call1((&module,))?.extract()?, module); + class_ids.insert(id_fn.call1((class,))?.extract()?); + } + Ok((modules, class_ids)) + })?; for module in modules.into_values() { self.register_module(module, &class_ids)?; @@ -380,7 +402,13 @@ impl Nac3 { ) }; let mut synthesized = parse_program(&synthesized).unwrap(); - let resolver = Arc::new(Resolver { + let builtins = PyModule::import(py, "builtins")?; + let helper = PythonHelper { + id_fn: builtins.getattr("id").unwrap().to_object(py), + len_fn: builtins.getattr("len").unwrap().to_object(py), + type_fn: builtins.getattr("type").unwrap().to_object(py), + }; + let resolver = Arc::new(Resolver(Arc::new(InnerResolver { id_to_type: self.builtins_ty.clone().into(), id_to_def: self.builtins_def.clone().into(), pyid_to_def: self.pyid_to_def.clone(), @@ -390,7 +418,8 @@ impl Nac3 { class_names: Default::default(), name_to_pyid, module: module.to_object(py), - }) as Arc; + helper, + }))) as Arc; let (_, def_id, _) = self .composer .register_top_level( @@ -455,9 +484,18 @@ impl Nac3 { passes.run_on(module); let (triple, features) = match isa { - Isa::Host => (TargetMachine::get_default_triple(), TargetMachine::get_host_cpu_features().to_string()), - Isa::RiscV32G => (TargetTriple::create("riscv32-unknown-linux"), "+a,+m,+f,+d".to_string()), - Isa::RiscV32IMA => (TargetTriple::create("riscv32-unknown-linux"), "+a,+m".to_string()), + Isa::Host => ( + TargetMachine::get_default_triple(), + TargetMachine::get_host_cpu_features().to_string(), + ), + Isa::RiscV32G => ( + TargetTriple::create("riscv32-unknown-linux"), + "+a,+m,+f,+d".to_string(), + ), + Isa::RiscV32IMA => ( + TargetTriple::create("riscv32-unknown-linux"), + "+a,+m".to_string(), + ), Isa::CortexA9 => ( TargetTriple::create("armv7-unknown-linux-gnueabihf"), "+dsp,+fp16,+neon,+vfp3".to_string(), @@ -482,6 +520,7 @@ impl Nac3 { &working_directory.join(&format!("{}.o", module.get_name().to_str().unwrap())), ) .expect("couldn't write module to file"); + println!("{}", module.print_to_string().to_str().unwrap()); }))); let thread_names: Vec = (0..4).map(|i| format!("module{}", i)).collect(); let threads: Vec<_> = thread_names @@ -503,11 +542,24 @@ impl Nac3 { filename.to_string(), ]; if isa != Isa::Host { - linker_args.push("-T".to_string() + self.working_directory.path().join("kernel.ld").to_str().unwrap()); + linker_args.push( + "-T".to_string() + + self + .working_directory + .path() + .join("kernel.ld") + .to_str() + .unwrap(), + ); } linker_args.extend(thread_names.iter().map(|name| { let name_o = name.to_owned() + ".o"; - self.working_directory.path().join(name_o.as_str()).to_str().unwrap().to_string() + self.working_directory + .path() + .join(name_o.as_str()) + .to_str() + .unwrap() + .to_string() })); if let Ok(linker_status) = Command::new("ld.lld").args(linker_args).status() { if !linker_status.success() { diff --git a/nac3artiq/src/symbol_resolver.rs b/nac3artiq/src/symbol_resolver.rs index 66e3e1ac..01310103 100644 --- a/nac3artiq/src/symbol_resolver.rs +++ b/nac3artiq/src/symbol_resolver.rs @@ -2,19 +2,19 @@ use inkwell::{types::BasicType, values::BasicValueEnum, AddressSpace}; use nac3core::{ codegen::CodeGenContext, location::Location, - symbol_resolver::{SymbolResolver, ValueEnum}, + symbol_resolver::{StaticValue, SymbolResolver, ValueEnum}, toplevel::{DefinitionId, TopLevelDef}, typecheck::{ type_inferencer::PrimitiveStore, typedef::{Type, TypeEnum, Unifier}, }, }; +use nac3parser::ast::StrRef; use parking_lot::{Mutex, RwLock}; use pyo3::{ types::{PyList, PyModule, PyTuple}, PyAny, PyObject, PyResult, Python, }; -use nac3parser::ast::StrRef; use std::{ cell::RefCell, collections::{HashMap, HashSet}, @@ -23,7 +23,7 @@ use std::{ use crate::PrimitivePythonId; -pub struct Resolver { +pub struct InnerResolver { pub id_to_type: Mutex>, pub id_to_def: Mutex>, pub global_value_ids: Arc>>, @@ -31,32 +31,95 @@ pub struct Resolver { pub pyid_to_def: Arc>>, pub pyid_to_type: Arc>>, pub primitive_ids: PrimitivePythonId, + pub helper: PythonHelper, // module specific pub name_to_pyid: HashMap, pub module: PyObject, } -struct PythonHelper<'a> { - type_fn: &'a PyAny, - len_fn: &'a PyAny, - id_fn: &'a PyAny, +pub struct Resolver(pub Arc); + +pub struct PythonHelper { + pub type_fn: PyObject, + pub len_fn: PyObject, + pub id_fn: PyObject, } -impl Resolver { +struct PythonValue { + id: u64, + value: PyObject, + resolver: Arc, +} + +impl StaticValue for PythonValue { + fn get_unique_identifier(&self) -> u64 { + self.id + } + + fn to_basic_value_enum<'ctx, 'a>( + &self, + ctx: &mut CodeGenContext<'ctx, 'a>, + ) -> BasicValueEnum<'ctx> { + Python::with_gil(|py| -> PyResult> { + self.resolver + .get_obj_value(py, self.value.as_ref(py), ctx) + .map(Option::unwrap) + }) + .unwrap() + } + + fn get_field<'ctx, 'a>( + &self, + name: StrRef, + ctx: &mut CodeGenContext<'ctx, 'a>, + ) -> Option> { + Python::with_gil(|py| -> PyResult>> { + 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)?; + 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.iter() { + if field_name == &name { + mutable = *is_mutable; + break; + } + } + } + Ok(if mutable { + None + } else { + println!("getting attribute {}", name); + let obj = self.value.getattr(py, &name.to_string())?; + let id = self.resolver.helper.id_fn.call1(py, (&obj,))?.extract(py)?; + Some(ValueEnum::Static(Arc::new(PythonValue { + id, + value: obj, + resolver: self.resolver.clone(), + }))) + }) + }) + .unwrap() + } +} + +impl InnerResolver { fn get_list_elem_type( &self, + py: Python, list: &PyAny, len: usize, - helper: &PythonHelper, unifier: &mut Unifier, defs: &[Arc>], primitives: &PrimitiveStore, ) -> PyResult> { - let first = self.get_obj_type(list.get_item(0)?, helper, unifier, defs, primitives)?; + let first = self.get_obj_type(py, list.get_item(0)?, unifier, defs, primitives)?; Ok((1..len).fold(first, |a, i| { let b = list .get_item(i) - .map(|elem| self.get_obj_type(elem, helper, unifier, defs, primitives)); + .map(|elem| self.get_obj_type(py, elem, unifier, defs, primitives)); a.and_then(|a| { if let Ok(Ok(Some(ty))) = b { if unifier.unify(a, ty).is_ok() { @@ -73,16 +136,17 @@ impl Resolver { fn get_obj_type( &self, + py: Python, obj: &PyAny, - helper: &PythonHelper, unifier: &mut Unifier, defs: &[Arc>], primitives: &PrimitiveStore, ) -> PyResult> { - let ty_id: u64 = helper + let ty_id: u64 = self + .helper .id_fn - .call1((helper.type_fn.call1((obj,))?,))? - .extract()?; + .call1(py, (self.helper.type_fn.call1(py, (obj,))?,))? + .extract(py)?; if ty_id == self.primitive_ids.int || ty_id == self.primitive_ids.int32 { Ok(Some(primitives.int32)) @@ -93,20 +157,20 @@ impl Resolver { } else if ty_id == self.primitive_ids.float { Ok(Some(primitives.float)) } else if ty_id == self.primitive_ids.list { - let len: usize = helper.len_fn.call1((obj,))?.extract()?; + let len: usize = self.helper.len_fn.call1(py, (obj,))?.extract(py)?; if len == 0 { let var = unifier.get_fresh_var().0; let list = unifier.add_ty(TypeEnum::TList { ty: var }); Ok(Some(list)) } else { - let ty = self.get_list_elem_type(obj, len, helper, unifier, defs, primitives)?; + let ty = self.get_list_elem_type(py, obj, len, unifier, defs, primitives)?; Ok(ty.map(|ty| unifier.add_ty(TypeEnum::TList { ty }))) } } else if ty_id == self.primitive_ids.tuple { let elements: &PyTuple = obj.cast_as()?; let types: Result>, _> = elements .iter() - .map(|elem| self.get_obj_type(elem, helper, unifier, defs, primitives)) + .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 }))) @@ -141,7 +205,7 @@ impl Resolver { let name: String = field.0.into(); let field_data = obj.getattr(&name)?; let ty = self - .get_obj_type(field_data, helper, unifier, defs, primitives)? + .get_obj_type(py, field_data, unifier, defs, primitives)? .unwrap_or(primitives.none); let field_ty = unifier.subst(field.1, &var_map).unwrap_or(field.1); if unifier.unify(ty, field_ty).is_err() { @@ -153,7 +217,7 @@ impl Resolver { for (_, ty) in var_map.iter() { // must be concrete type if !unifier.is_concrete(*ty, &[]) { - return Ok(None) + return Ok(None); } } Ok(Some(unifier.add_ty(TypeEnum::TObj { @@ -172,14 +236,15 @@ impl Resolver { fn get_obj_value<'ctx, 'a>( &self, + py: Python, obj: &PyAny, - helper: &PythonHelper, ctx: &mut CodeGenContext<'ctx, 'a>, ) -> PyResult>> { - let ty_id: u64 = helper + let ty_id: u64 = self + .helper .id_fn - .call1((helper.type_fn.call1((obj,))?,))? - .extract()?; + .call1(py, (self.helper.type_fn.call1(py, (obj,))?,))? + .extract(py)?; if ty_id == self.primitive_ids.int || ty_id == self.primitive_ids.int32 { let val: i32 = obj.extract()?; Ok(Some(ctx.ctx.i32_type().const_int(val as u64, false).into())) @@ -195,16 +260,16 @@ impl Resolver { let val: f64 = obj.extract()?; Ok(Some(ctx.ctx.f64_type().const_float(val).into())) } else if ty_id == self.primitive_ids.list { - let id: u64 = helper.id_fn.call1((obj,))?.extract()?; + let id: u64 = self.helper.id_fn.call1(py, (obj,))?.extract(py)?; let id_str = id.to_string(); - let len: usize = helper.len_fn.call1((obj,))?.extract()?; + let len: usize = self.helper.len_fn.call1(py, (obj,))?.extract(py)?; let ty = if len == 0 { ctx.primitives.int32 } else { self.get_list_elem_type( + py, obj, len, - helper, &mut ctx.unifier, &ctx.top_level.definitions.read(), &ctx.primitives, @@ -236,7 +301,7 @@ impl Resolver { let arr: Result>, _> = (0..len) .map(|i| { obj.get_item(i) - .and_then(|elem| self.get_obj_value(elem, helper, ctx)) + .and_then(|elem| self.get_obj_value(py, elem, ctx)) }) .collect(); let arr = arr?.unwrap(); @@ -297,15 +362,15 @@ impl Resolver { Ok(Some(global.as_pointer_value().into())) } else if ty_id == self.primitive_ids.tuple { - let id: u64 = helper.id_fn.call1((obj,))?.extract()?; + let id: u64 = self.helper.id_fn.call1(py, (obj,))?.extract(py)?; let id_str = id.to_string(); let elements: &PyTuple = obj.cast_as()?; let types: Result>, _> = elements .iter() .map(|elem| { self.get_obj_type( + py, elem, - helper, &mut ctx.unifier, &ctx.top_level.definitions.read(), &ctx.primitives, @@ -331,7 +396,7 @@ impl Resolver { let val: Result>, _> = elements .iter() - .map(|elem| self.get_obj_value(elem, helper, ctx)) + .map(|elem| self.get_obj_value(py, elem, ctx)) .collect(); let val = val?.unwrap(); let val = ctx.ctx.const_struct(&val, false); @@ -341,17 +406,11 @@ impl Resolver { global.set_initializer(&val); Ok(Some(global.as_pointer_value().into())) } else { - let id: u64 = helper.id_fn.call1((obj,))?.extract()?; + let id: u64 = self.helper.id_fn.call1(py, (obj,))?.extract(py)?; let id_str = id.to_string(); let top_level_defs = ctx.top_level.definitions.read(); let ty = self - .get_obj_type( - obj, - helper, - &mut ctx.unifier, - &top_level_defs, - &ctx.primitives, - )? + .get_obj_type(py, obj, &mut ctx.unifier, &top_level_defs, &ctx.primitives)? .unwrap(); let ty = ctx .get_llvm_type(ty) @@ -380,7 +439,7 @@ impl Resolver { let values: Result>, _> = fields .iter() .map(|(name, _, _)| { - self.get_obj_value(obj.getattr(&name.to_string())?, helper, ctx) + self.get_obj_value(py, obj.getattr(&name.to_string())?, ctx) }) .collect(); let values = values?; @@ -409,13 +468,13 @@ impl SymbolResolver for Resolver { primitives: &PrimitiveStore, str: StrRef, ) -> Option { - let mut id_to_type = self.id_to_type.lock(); + let mut id_to_type = self.0.id_to_type.lock(); id_to_type.get(&str).cloned().or_else(|| { - let py_id = self.name_to_pyid.get(&str); + let py_id = self.0.name_to_pyid.get(&str); let result = py_id.and_then(|id| { - self.pyid_to_type.read().get(id).copied().or_else(|| { + self.0.pyid_to_type.read().get(id).copied().or_else(|| { Python::with_gil(|py| -> PyResult> { - let obj: &PyAny = self.module.extract(py)?; + let obj: &PyAny = self.0.module.extract(py)?; let members: &PyList = PyModule::import(py, "inspect")? .getattr("getmembers")? .call1((obj,))? @@ -424,15 +483,9 @@ impl SymbolResolver for Resolver { for member in members.iter() { let key: &str = member.get_item(0)?.extract()?; if key == str.to_string() { - let builtins = PyModule::import(py, "builtins")?; - let helper = PythonHelper { - id_fn: builtins.getattr("id").unwrap(), - len_fn: builtins.getattr("len").unwrap(), - type_fn: builtins.getattr("type").unwrap(), - }; - sym_ty = self.get_obj_type( + sym_ty = self.0.get_obj_type( + py, member.get_item(1)?, - &helper, unifier, defs, primitives, @@ -455,10 +508,10 @@ impl SymbolResolver for Resolver { fn get_symbol_value<'ctx, 'a>( &self, id: StrRef, - ctx: &mut CodeGenContext<'ctx, 'a>, + _: &mut CodeGenContext<'ctx, 'a>, ) -> Option> { Python::with_gil(|py| -> PyResult>> { - let obj: &PyAny = self.module.extract(py)?; + let obj: &PyAny = self.0.module.extract(py)?; let members: &PyList = PyModule::import(py, "inspect")? .getattr("getmembers")? .call1((obj,))? @@ -468,17 +521,16 @@ impl SymbolResolver for Resolver { let key: &str = member.get_item(0)?.extract()?; let val = member.get_item(1)?; if key == id.to_string() { - let builtins = PyModule::import(py, "builtins")?; - let helper = PythonHelper { - id_fn: builtins.getattr("id").unwrap(), - len_fn: builtins.getattr("len").unwrap(), - type_fn: builtins.getattr("type").unwrap(), - }; - sym_value = self.get_obj_value(val, &helper, ctx)?; + let id = self.0.helper.id_fn.call1(py, (val,))?.extract(py)?; + sym_value = Some(PythonValue { + id, + value: val.extract()?, + resolver: self.0.clone(), + }); break; } } - Ok(sym_value.map(|v| v.into())) + Ok(sym_value.map(|v| ValueEnum::Static(Arc::new(v)))) }) .unwrap() } @@ -488,10 +540,10 @@ impl SymbolResolver for Resolver { } fn get_identifier_def(&self, id: StrRef) -> Option { - let mut id_to_def = self.id_to_def.lock(); + let mut id_to_def = self.0.id_to_def.lock(); id_to_def.get(&id).cloned().or_else(|| { - let py_id = self.name_to_pyid.get(&id); - let result = py_id.and_then(|id| self.pyid_to_def.read().get(id).copied()); + let py_id = self.0.name_to_pyid.get(&id); + let result = py_id.and_then(|id| self.0.pyid_to_def.read().get(id).copied()); if let Some(result) = &result { id_to_def.insert(id, *result); } diff --git a/nac3core/src/codegen/expr.rs b/nac3core/src/codegen/expr.rs index 678c9a2c..94a1024a 100644 --- a/nac3core/src/codegen/expr.rs +++ b/nac3core/src/codegen/expr.rs @@ -676,7 +676,14 @@ pub fn gen_expr<'ctx, 'a, G: CodeGenerator + ?Sized>( ExprKind::Attribute { value, attr, .. } => { // note that we would handle class methods directly in calls match generator.gen_expr(ctx, value).unwrap() { - ValueEnum::Static(v) => v.get_field(*attr, ctx).unwrap(), + ValueEnum::Static(v) => v.get_field(*attr, ctx).unwrap_or_else(|| { + let v = v.to_basic_value_enum(ctx); + let index = ctx.get_attr_index(value.custom.unwrap(), *attr); + ValueEnum::Dynamic(ctx.build_gep_and_load( + v.into_pointer_value(), + &[zero, int32.const_int(index as u64, false)], + )) + }), ValueEnum::Dynamic(v) => { let index = ctx.get_attr_index(value.custom.unwrap(), *attr); ValueEnum::Dynamic(ctx.build_gep_and_load(