Compare commits
1 Commits
2cfa6091f3
...
e053872b10
Author | SHA1 | Date |
---|---|---|
David Mak | e053872b10 |
|
@ -28,8 +28,9 @@ use pyo3::create_exception;
|
|||
use parking_lot::{Mutex, RwLock};
|
||||
|
||||
use nac3core::{
|
||||
codegen::irrt::load_irrt,
|
||||
codegen::{concrete_type::ConcreteTypeStore, CodeGenTask, WithCall, WorkerRegistry},
|
||||
codegen::irrt::load_irrt,
|
||||
codegen::tracert::TraceRuntimeConfig,
|
||||
symbol_resolver::SymbolResolver,
|
||||
toplevel::{
|
||||
composer::{ComposerConfig, TopLevelComposer},
|
||||
|
@ -608,6 +609,7 @@ impl Nac3 {
|
|||
threads,
|
||||
top_level.clone(),
|
||||
&self.llvm_options,
|
||||
&TraceRuntimeConfig::default(),
|
||||
&f
|
||||
);
|
||||
registry.add_task(task);
|
||||
|
|
|
@ -4,6 +4,9 @@ version = "0.1.0"
|
|||
authors = ["M-Labs"]
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
tracing = []
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.13"
|
||||
crossbeam = "0.8"
|
||||
|
@ -23,4 +26,5 @@ indoc = "2.0"
|
|||
insta = "=1.11.0"
|
||||
|
||||
[build-dependencies]
|
||||
itertools = "0.13"
|
||||
regex = "1.10"
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use regex::Regex;
|
||||
use std::{
|
||||
env,
|
||||
fs::File,
|
||||
|
@ -6,17 +5,29 @@ use std::{
|
|||
path::Path,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
use std::ffi::OsStr;
|
||||
|
||||
fn main() {
|
||||
const FILE: &str = "src/codegen/irrt/irrt.c";
|
||||
use itertools::Itertools;
|
||||
use regex::Regex;
|
||||
|
||||
/// Extracts the extension-less filename from a [`Path`].
|
||||
fn path_to_extless_filename(path: &Path) -> &str {
|
||||
path.file_name()
|
||||
.map(Path::new)
|
||||
.and_then(Path::file_stem)
|
||||
.and_then(OsStr::to_str)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Compiles a source C file into LLVM bitcode.
|
||||
fn compile_file_to_ir(path: &Path, filename_without_ext: &str) {
|
||||
/*
|
||||
* HACK: Sadly, clang doesn't let us emit generic LLVM bitcode.
|
||||
* Compiling for WASM32 and filtering the output with regex is the closest we can get.
|
||||
*/
|
||||
let flags: &[&str] = &[
|
||||
"--target=wasm32",
|
||||
FILE,
|
||||
path.to_str().unwrap(),
|
||||
"-fno-discard-value-names",
|
||||
match env::var("PROFILE").as_deref() {
|
||||
Ok("debug") => "-O0",
|
||||
|
@ -31,7 +42,7 @@ fn main() {
|
|||
"-",
|
||||
];
|
||||
|
||||
println!("cargo:rerun-if-changed={FILE}");
|
||||
println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let out_path = Path::new(&out_dir);
|
||||
|
||||
|
@ -44,8 +55,7 @@ fn main() {
|
|||
})
|
||||
.unwrap();
|
||||
|
||||
// https://github.com/rust-lang/regex/issues/244
|
||||
let output = std::str::from_utf8(&output.stdout).unwrap().replace("\r\n", "\n");
|
||||
let output = std::str::from_utf8(&output.stdout).unwrap();
|
||||
let mut filtered_output = String::with_capacity(output.len());
|
||||
|
||||
let regex_filter = Regex::new(r"(?ms:^define.*?\}$)|(?m:^declare.*?$)").unwrap();
|
||||
|
@ -61,18 +71,36 @@ fn main() {
|
|||
|
||||
println!("cargo:rerun-if-env-changed=DEBUG_DUMP_IRRT");
|
||||
if env::var("DEBUG_DUMP_IRRT").is_ok() {
|
||||
let mut file = File::create(out_path.join("irrt.ll")).unwrap();
|
||||
let mut file = File::create(out_path.join(format!("{filename_without_ext}.ll"))).unwrap();
|
||||
file.write_all(output.as_bytes()).unwrap();
|
||||
let mut file = File::create(out_path.join("irrt-filtered.ll")).unwrap();
|
||||
let mut file = File::create(out_path.join(format!("{filename_without_ext}-filtered.ll"))).unwrap();
|
||||
file.write_all(filtered_output.as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
let mut llvm_as = Command::new("llvm-as-irrt")
|
||||
.stdin(Stdio::piped())
|
||||
.arg("-o")
|
||||
.arg(out_path.join("irrt.bc"))
|
||||
.arg(out_path.join(format!("{filename_without_ext}.bc")))
|
||||
.spawn()
|
||||
.unwrap();
|
||||
llvm_as.stdin.as_mut().unwrap().write_all(filtered_output.as_bytes()).unwrap();
|
||||
assert!(llvm_as.wait().unwrap().success());
|
||||
}
|
||||
|
||||
fn main() {
|
||||
const IRRT_SOURCE_PATHS: &[&str] = &[
|
||||
"src/codegen/irrt/irrt.c",
|
||||
"src/codegen/tracert/tracert.c",
|
||||
];
|
||||
|
||||
assert!(IRRT_SOURCE_PATHS.iter()
|
||||
.map(Path::new)
|
||||
.map(path_to_extless_filename)
|
||||
.all_unique());
|
||||
|
||||
for path in IRRT_SOURCE_PATHS {
|
||||
let path = Path::new(path);
|
||||
|
||||
compile_file_to_ir(path, path_to_extless_filename(path))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use std::iter::once;
|
||||
use inkwell::AddressSpace;
|
||||
use inkwell::attributes::{Attribute, AttributeLoc};
|
||||
use inkwell::values::{BasicValueEnum, CallSiteValue, FloatValue, IntValue};
|
||||
use itertools::Either;
|
||||
use inkwell::values::{BasicValue, BasicValueEnum, CallSiteValue, FloatValue, IntValue};
|
||||
use itertools::{Either, Itertools};
|
||||
|
||||
use crate::codegen::CodeGenContext;
|
||||
|
||||
|
@ -610,4 +612,47 @@ pub fn call_nextafter<'ctx>(
|
|||
.map(|v| v.map_left(BasicValueEnum::into_float_value))
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Invokes the `printf` function.
|
||||
pub fn call_printf<'ctx>(
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
format: &str,
|
||||
args: &[BasicValueEnum<'ctx>],
|
||||
) -> IntValue<'ctx> {
|
||||
const FN_NAME: &str = "printf";
|
||||
|
||||
let llvm_i8 = ctx.ctx.i8_type();
|
||||
let llvm_i32 = ctx.ctx.i32_type();
|
||||
let llvm_pi8 = llvm_i8.ptr_type(AddressSpace::default());
|
||||
|
||||
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
|
||||
let fn_type = llvm_i32.fn_type(&[llvm_pi8.into()], true);
|
||||
let func = ctx.module.add_function(FN_NAME, fn_type, None);
|
||||
for attr in ["nofree", "nounwind"] {
|
||||
func.add_attribute(
|
||||
AttributeLoc::Function,
|
||||
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
|
||||
);
|
||||
}
|
||||
|
||||
func
|
||||
});
|
||||
|
||||
let pformat = ctx.builder.build_global_string_ptr(&format!("{format}\0"), "")
|
||||
.map(|v| v.as_basic_value_enum())
|
||||
.map(BasicValueEnum::into_pointer_value)
|
||||
.unwrap();
|
||||
|
||||
let fn_args = once(&pformat.as_basic_value_enum()).chain(args)
|
||||
.cloned()
|
||||
.map(BasicValueEnum::into)
|
||||
.collect_vec();
|
||||
|
||||
ctx.builder
|
||||
.build_call(extern_fn, fn_args.as_slice(), "")
|
||||
.map(CallSiteValue::try_as_basic_value)
|
||||
.map(|v| v.map_left(BasicValueEnum::into_int_value))
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap()
|
||||
}
|
||||
|
|
|
@ -50,12 +50,17 @@ pub mod irrt;
|
|||
pub mod llvm_intrinsics;
|
||||
pub mod numpy;
|
||||
pub mod stmt;
|
||||
pub mod tracert;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
use concrete_type::{ConcreteType, ConcreteTypeEnum, ConcreteTypeStore};
|
||||
pub use generator::{CodeGenerator, DefaultCodeGenerator};
|
||||
use tracert::TraceRuntimeConfig;
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
use crate::codegen::tracert::TraceRuntimeState;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct StaticValueStore {
|
||||
|
@ -200,6 +205,9 @@ pub struct CodeGenContext<'ctx, 'a> {
|
|||
/// See [need_sret].
|
||||
pub need_sret: bool,
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
pub tracert_state: Option<TraceRuntimeState>,
|
||||
|
||||
/// The current source location.
|
||||
pub current_loc: Location,
|
||||
}
|
||||
|
@ -248,6 +256,9 @@ pub struct WorkerRegistry {
|
|||
|
||||
/// LLVM-related options for code generation.
|
||||
pub llvm_options: CodeGenLLVMOptions,
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
tracert_config: TraceRuntimeConfig,
|
||||
}
|
||||
|
||||
impl WorkerRegistry {
|
||||
|
@ -258,6 +269,7 @@ impl WorkerRegistry {
|
|||
generators: Vec<Box<G>>,
|
||||
top_level_ctx: Arc<TopLevelContext>,
|
||||
llvm_options: &CodeGenLLVMOptions,
|
||||
_tracert_config: &TraceRuntimeConfig,
|
||||
f: &Arc<WithCall>,
|
||||
) -> (Arc<WorkerRegistry>, Vec<thread::JoinHandle<()>>) {
|
||||
let (sender, receiver) = unbounded();
|
||||
|
@ -279,6 +291,8 @@ impl WorkerRegistry {
|
|||
wait_condvar,
|
||||
top_level_ctx,
|
||||
llvm_options: llvm_options.clone(),
|
||||
#[cfg(feature = "tracing")]
|
||||
tracert_config: _tracert_config.clone(),
|
||||
});
|
||||
|
||||
let mut handles = Vec::new();
|
||||
|
@ -875,6 +889,12 @@ pub fn gen_func_impl<'ctx, G: CodeGenerator, F: FnOnce(&mut G, &mut CodeGenConte
|
|||
unifier,
|
||||
static_value_store,
|
||||
need_sret: has_sret,
|
||||
#[cfg(feature = "tracing")]
|
||||
tracert_state: if registry.tracert_config.enabled_tags.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(TraceRuntimeState::create(registry.tracert_config.clone()))
|
||||
},
|
||||
current_loc: Location::default(),
|
||||
debug_info: (dibuilder, compile_unit, func_scope.as_debug_info_scope()),
|
||||
};
|
||||
|
|
|
@ -2,7 +2,8 @@ use crate::{
|
|||
codegen::{
|
||||
classes::{ListType, NDArrayType, ProxyType, RangeType},
|
||||
concrete_type::ConcreteTypeStore, CodeGenContext, CodeGenerator, CodeGenLLVMOptions,
|
||||
CodeGenTargetMachineOptions, CodeGenTask, DefaultCodeGenerator, WithCall, WorkerRegistry,
|
||||
CodeGenTargetMachineOptions, CodeGenTask, DefaultCodeGenerator, tracert::TraceRuntimeConfig,
|
||||
WithCall, WorkerRegistry,
|
||||
},
|
||||
symbol_resolver::{SymbolResolver, ValueEnum},
|
||||
toplevel::{
|
||||
|
@ -231,6 +232,7 @@ fn test_primitives() {
|
|||
threads,
|
||||
top_level,
|
||||
&llvm_options,
|
||||
&TraceRuntimeConfig::default(),
|
||||
&f
|
||||
);
|
||||
registry.add_task(task);
|
||||
|
@ -421,6 +423,7 @@ fn test_simple_call() {
|
|||
threads,
|
||||
top_level,
|
||||
&llvm_options,
|
||||
&TraceRuntimeConfig::default(),
|
||||
&f
|
||||
);
|
||||
registry.add_task(task);
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
use inkwell::AtomicOrdering;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::memory_buffer::MemoryBuffer;
|
||||
use inkwell::module::Module;
|
||||
use inkwell::values::BasicValueEnum;
|
||||
use crate::codegen::{CodeGenContext, extern_fns};
|
||||
|
||||
#[derive(Eq, Clone, PartialEq)]
|
||||
pub struct TraceRuntimeConfig {
|
||||
pub enabled_tags: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for TraceRuntimeConfig {
|
||||
fn default() -> Self {
|
||||
TraceRuntimeConfig {
|
||||
enabled_tags: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
#[derive(Eq, Clone, PartialEq)]
|
||||
pub struct TraceRuntimeState {
|
||||
config: TraceRuntimeConfig,
|
||||
indent: usize,
|
||||
}
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
impl TraceRuntimeState {
|
||||
pub fn create(config: TraceRuntimeConfig) -> TraceRuntimeState {
|
||||
TraceRuntimeState {
|
||||
config,
|
||||
indent: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn load_tracert<'ctx>(ctx: &'ctx Context, config: &TraceRuntimeConfig) -> Option<Module<'ctx>> {
|
||||
#[cfg(feature = "tracing")]
|
||||
{
|
||||
if !config.enabled_tags.is_empty() {
|
||||
let bitcode_buf = MemoryBuffer::create_from_memory_range(
|
||||
include_bytes!(concat!(env!("OUT_DIR"), "/tracert.bc")),
|
||||
"tracert_bitcode_buffer",
|
||||
);
|
||||
let module = Module::parse_bitcode_from_buffer(&bitcode_buf, ctx).unwrap();
|
||||
|
||||
return Some(module)
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// TODO: Might need to redesign how trace logging should be done
|
||||
|
||||
pub fn trace_log<'ctx>(
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
tag: &'static str,
|
||||
format: &'static str,
|
||||
args: &[BasicValueEnum<'ctx>],
|
||||
) {
|
||||
#[cfg(feature = "tracing")]
|
||||
{
|
||||
if let None = ctx.tracert_state {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Add indentation
|
||||
let str = format!("[TRACING] {tag} - {format}\n\0");
|
||||
extern_fns::call_printf(ctx, &str, args);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trace_log_with_location<'ctx>(
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
tag: &'static str,
|
||||
format: &str,
|
||||
args: &[BasicValueEnum<'ctx>],
|
||||
file: &'static str,
|
||||
line: u32,
|
||||
column: u32,
|
||||
) {
|
||||
#[cfg(feature = "tracing")]
|
||||
{
|
||||
if let None = ctx.tracert_state {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Add indentation
|
||||
let str = format!("[TRACING] {file}:{line}:{column}:{tag} - {format}\n\0");
|
||||
extern_fns::call_printf(ctx, &str, args);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trace_push_level<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>) {
|
||||
#[cfg(feature = "tracing")]
|
||||
{
|
||||
if let Some(tracert_state) = &mut ctx.tracert_state {
|
||||
debug_assert!(tracert_state.indent < usize::MAX);
|
||||
if tracert_state.indent < usize::MAX {
|
||||
tracert_state.indent += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trace_pop_level<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>) {
|
||||
#[cfg(feature = "tracing")]
|
||||
{
|
||||
if let Some(tracert_state) = &mut ctx.tracert_state {
|
||||
debug_assert!(tracert_state.indent > 0);
|
||||
if tracert_state.indent > 0 {
|
||||
tracert_state.indent -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mfence<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>) {
|
||||
if cfg!(feature = "tracing") {
|
||||
ctx.builder.build_fence(AtomicOrdering::SequentiallyConsistent, 0, "").unwrap();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
// stdio.h
|
||||
int printf(const char *format, ...);
|
|
@ -4,6 +4,9 @@ version = "0.1.0"
|
|||
authors = ["M-Labs"]
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
tracing = ["nac3core/tracing"]
|
||||
|
||||
[dependencies]
|
||||
parking_lot = "0.12"
|
||||
nac3parser = { path = "../nac3parser" }
|
||||
|
|
|
@ -73,13 +73,20 @@ void output_int32_list(struct cslice *slice) {
|
|||
putchar('\n');
|
||||
}
|
||||
|
||||
void output_str(struct cslice *slice) {
|
||||
const char *data = (const char *) slice->data;
|
||||
void output_str_impl(const struct cslice* slice, bool newline) {
|
||||
const char *data = (const char *) slice->data;
|
||||
|
||||
for (usize i = 0; i < slice->len; ++i) {
|
||||
putchar(data[i]);
|
||||
}
|
||||
putchar('\n');
|
||||
for (usize i = 0; i < slice->len; ++i) {
|
||||
putchar(data[i]);
|
||||
}
|
||||
|
||||
if (newline) {
|
||||
putchar('\n');
|
||||
}
|
||||
}
|
||||
|
||||
void output_str(struct cslice *slice) {
|
||||
output_str_impl(slice, true);
|
||||
}
|
||||
|
||||
uint64_t dbg_stack_address(__attribute__((unused)) struct cslice *slice) {
|
||||
|
@ -94,8 +101,34 @@ uint32_t __nac3_personality(uint32_t state, uint32_t exception_object, uint32_t
|
|||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
struct exception {
|
||||
uint32_t name;
|
||||
struct cslice file;
|
||||
uint32_t line;
|
||||
uint32_t col;
|
||||
struct cslice func;
|
||||
struct cslice message;
|
||||
uint64_t param0;
|
||||
uint64_t param1;
|
||||
uint64_t param2;
|
||||
};
|
||||
|
||||
void output_exception(const struct exception *ex) {
|
||||
fputs("exception { location: ", stdout);
|
||||
output_str_impl(&ex->file, false);
|
||||
printf(":%u:%u, func: ", ex->line, ex->col);
|
||||
output_str_impl(&ex->func, false);
|
||||
fputs(", message: ", stdout);
|
||||
output_str_impl(&ex->message, false);
|
||||
printf(", params: [%lu, %lu, %lu] }\n", ex->param0, ex->param1, ex->param2);
|
||||
}
|
||||
|
||||
uint32_t __nac3_raise(uint32_t state, uint32_t exception_object, uint32_t context) {
|
||||
printf("__nac3_raise(state: %u, exception_object: %u, context: %u)\n", state, exception_object, context);
|
||||
printf("__nac3_raise(state: %#10x, exception_object: %#10x, context: %#10x)\n", state, exception_object, context);
|
||||
|
||||
struct exception *ex = (struct exception *)((0x7ffffffful << 16) | state);
|
||||
output_exception(ex);
|
||||
|
||||
exit(101);
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
|
|
@ -13,7 +13,8 @@ use std::collections::HashSet;
|
|||
use nac3core::{
|
||||
codegen::{
|
||||
concrete_type::ConcreteTypeStore, irrt::load_irrt, CodeGenLLVMOptions,
|
||||
CodeGenTargetMachineOptions, CodeGenTask, DefaultCodeGenerator, WithCall, WorkerRegistry,
|
||||
CodeGenTargetMachineOptions, CodeGenTask, DefaultCodeGenerator,
|
||||
tracert::{load_tracert, TraceRuntimeConfig}, WithCall, WorkerRegistry,
|
||||
},
|
||||
symbol_resolver::SymbolResolver,
|
||||
toplevel::{
|
||||
|
@ -68,6 +69,9 @@ struct CommandLineArgs {
|
|||
/// Additional target features to enable/disable, specified using the `+`/`-` prefixes.
|
||||
#[arg(long)]
|
||||
target_features: Option<String>,
|
||||
|
||||
#[arg(long)]
|
||||
trace: Vec<String>,
|
||||
}
|
||||
|
||||
fn handle_typevar_definition(
|
||||
|
@ -258,6 +262,7 @@ fn main() {
|
|||
triple,
|
||||
mcpu,
|
||||
target_features,
|
||||
trace,
|
||||
} = cli;
|
||||
|
||||
Target::initialize_all(&InitializationConfig::default());
|
||||
|
@ -289,6 +294,9 @@ fn main() {
|
|||
// The default behavior for -O<n> where n>3 defaults to O3 for both Clang and GCC
|
||||
_ => OptimizationLevel::Aggressive,
|
||||
};
|
||||
let tracert_config = TraceRuntimeConfig {
|
||||
enabled_tags: trace,
|
||||
};
|
||||
|
||||
let program = match fs::read_to_string(file_name.clone()) {
|
||||
Ok(program) => program,
|
||||
|
@ -405,7 +413,13 @@ fn main() {
|
|||
let threads = (0..threads)
|
||||
.map(|i| Box::new(DefaultCodeGenerator::new(format!("module{i}"), SIZE_T)))
|
||||
.collect();
|
||||
let (registry, handles) = WorkerRegistry::create_workers(threads, top_level, &llvm_options, &f);
|
||||
let (registry, handles) = WorkerRegistry::create_workers(
|
||||
threads,
|
||||
top_level,
|
||||
&llvm_options,
|
||||
&tracert_config,
|
||||
&f,
|
||||
);
|
||||
registry.add_task(task);
|
||||
registry.wait_tasks_complete(handles);
|
||||
|
||||
|
@ -436,6 +450,14 @@ fn main() {
|
|||
}
|
||||
main.link_in_module(irrt).unwrap();
|
||||
|
||||
if let Some(tracert) = load_tracert(&context, &tracert_config) {
|
||||
if emit_llvm {
|
||||
tracert.write_bitcode_to_path(Path::new("tracert.bc"));
|
||||
}
|
||||
|
||||
main.link_in_module(tracert).unwrap();
|
||||
}
|
||||
|
||||
let mut function_iter = main.get_first_function();
|
||||
while let Some(func) = function_iter {
|
||||
if func.count_basic_blocks() > 0 && func.get_name().to_str().unwrap() != "run" {
|
||||
|
|
Loading…
Reference in New Issue