diff --git a/crates/mun_codegen/src/intrinsics.rs b/crates/mun_codegen/src/intrinsics.rs index 44b2e8664..c52fb51d7 100644 --- a/crates/mun_codegen/src/intrinsics.rs +++ b/crates/mun_codegen/src/intrinsics.rs @@ -21,7 +21,4 @@ pub trait Intrinsic: Sync { intrinsics! { /// Allocates memory for the specified `type` in the allocator referred to by `alloc_handle`. pub fn new(type: *const TypeInfo, alloc_handle: *mut ffi::c_void) -> *const *mut ffi::c_void; - /// Allocates memory for and clones the specified type located at `src` into it. Memory is - /// allocated in the allocator referred to by `alloc_handle`. - pub fn clone(src: *const ffi::c_void, alloc_handle: *mut ffi::c_void) -> *const *mut ffi::c_void; } diff --git a/crates/mun_codegen/src/ir/intrinsics.rs b/crates/mun_codegen/src/ir/intrinsics.rs index a391b78f4..fc59b6a3d 100644 --- a/crates/mun_codegen/src/ir/intrinsics.rs +++ b/crates/mun_codegen/src/ir/intrinsics.rs @@ -37,8 +37,7 @@ fn collect_expr( match infer[*callee].as_callable_def() { Some(hir::CallableDef::Struct(_)) => { collect_intrinsic(db, entries, &intrinsics::new); - collect_intrinsic(db, entries, &intrinsics::clone); - // self.collect_intrinsic(db, entries, &intrinsics::drop); + // self.collect_intrinsic(module, entries, &intrinsics::drop); *needs_alloc = true; } Some(hir::CallableDef::Function(_)) => (), @@ -48,8 +47,7 @@ fn collect_expr( if let Expr::RecordLit { .. } = expr { collect_intrinsic(db, entries, &intrinsics::new); - collect_intrinsic(db, entries, &intrinsics::clone); - // self.collect_intrinsic(db, entries, &intrinsics::drop); + // self.collect_intrinsic(module, entries, &intrinsics::drop); *needs_alloc = true; } @@ -62,8 +60,7 @@ fn collect_expr( if let hir::Resolution::Def(hir::ModuleDef::Struct(_)) = resolution { collect_intrinsic(db, entries, &intrinsics::new); - collect_intrinsic(db, entries, &intrinsics::clone); - // self.collect_intrinsic(db, entries, &intrinsics::drop); + // self.collect_intrinsic( module, entries, &intrinsics::drop); *needs_alloc = true; } } @@ -88,7 +85,6 @@ pub fn collect_wrapper_body( needs_alloc: &mut bool, ) { collect_intrinsic(db, entries, &intrinsics::new); - collect_intrinsic(db, entries, &intrinsics::clone); - // self.collect_intrinsic(db, entries, &intrinsics::drop); + // self.collect_intrinsic(entries, &intrinsics::drop, module); *needs_alloc = true; } diff --git a/crates/mun_codegen/src/snapshots/test__field_crash_file_ir.snap b/crates/mun_codegen/src/snapshots/test__field_crash_file_ir.snap index 5a43277b4..225fe0f57 100644 --- a/crates/mun_codegen/src/snapshots/test__field_crash_file_ir.snap +++ b/crates/mun_codegen/src/snapshots/test__field_crash_file_ir.snap @@ -5,13 +5,13 @@ expression: "struct(gc) Foo { a: int };\n\nfn main(c:int):int {\n let b = Foo ; ModuleID = 'main.mun' source_filename = "main.mun" -%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } +%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } %struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } %Foo = type { i64 } @allocatorHandle = external global i8* @dispatchTable = external global %DispatchTable -@global_type_table = external global [6 x %struct.MunTypeInfo addrspace(4)*] +@global_type_table = external global [5 x %struct.MunTypeInfo addrspace(4)*] define i64 @main(i64) { body: @@ -21,8 +21,8 @@ body: %c1 = load i64, i64* %c %add = add i64 %c1, 5 %init = insertvalue %Foo undef, i64 %add, 0 - %new_ptr = load i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 1) - %Foo_ptr = load %struct.MunTypeInfo addrspace(4)*, %struct.MunTypeInfo addrspace(4)** getelementptr inbounds ([6 x %struct.MunTypeInfo addrspace(4)*], [6 x %struct.MunTypeInfo addrspace(4)*]* @global_type_table, i32 0, i32 0) + %new_ptr = load i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 0) + %Foo_ptr = load %struct.MunTypeInfo addrspace(4)*, %struct.MunTypeInfo addrspace(4)** getelementptr inbounds ([5 x %struct.MunTypeInfo addrspace(4)*], [5 x %struct.MunTypeInfo addrspace(4)*]* @global_type_table, i32 0, i32 0) %type_info_ptr_to_i8_ptr = bitcast %struct.MunTypeInfo addrspace(4)* %Foo_ptr to i8 addrspace(4)* %allocator_handle = load i8*, i8** @allocatorHandle %new = call i8* addrspace(4)* %new_ptr(i8 addrspace(4)* %type_info_ptr_to_i8_ptr, i8* %allocator_handle) diff --git a/crates/mun_codegen/src/snapshots/test__field_crash_group_ir.snap b/crates/mun_codegen/src/snapshots/test__field_crash_group_ir.snap index df07cfe1d..6f4018a84 100644 --- a/crates/mun_codegen/src/snapshots/test__field_crash_group_ir.snap +++ b/crates/mun_codegen/src/snapshots/test__field_crash_group_ir.snap @@ -7,7 +7,7 @@ source_filename = "group_name" %struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } %struct.MunStructInfo = type { i8 addrspace(4)*, i8 addrspace(4)* addrspace(4)*, %struct.MunTypeInfo addrspace(4)* addrspace(4)*, i16 addrspace(4)*, i16, i8 } -%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } +%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } @"type_info::::name" = private unnamed_addr constant [4 x i8] c"Foo\00" @"struct_info::::name" = private unnamed_addr constant [4 x i8] c"Foo\00" @@ -22,11 +22,9 @@ source_filename = "group_name" @"type_info::<*const TypeInfo>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"=\A1-\1F\C2\A7\88`d\90\F4\B5\BEE}x", [16 x i8]* @"type_info::<*const TypeInfo>::name", i32 64, i8 8, i8 0 } @"type_info::<*const *mut core::void>::name" = private unnamed_addr constant [23 x i8] c"*const *mut core::void\00" @"type_info::<*const *mut core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\C5fO\BD\84\DF\06\BFd+\B1\9Abv\CE\00", [23 x i8]* @"type_info::<*const *mut core::void>::name", i32 64, i8 8, i8 0 } -@"type_info::<*const core::void>::name" = private unnamed_addr constant [18 x i8] c"*const core::void\00" -@"type_info::<*const core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\EF\D3\E0ac~\5C\D4\EF\AE\B1}\CA\BE\DA\16", [18 x i8]* @"type_info::<*const core::void>::name", i32 64, i8 8, i8 0 } @"type_info::<*mut core::void>::name" = private unnamed_addr constant [16 x i8] c"*mut core::void\00" @"type_info::<*mut core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\F0Y\22\FC\95\9E\7F\CE\08T\B1\A2\CD\A7\FAz", [16 x i8]* @"type_info::<*mut core::void>::name", i32 64, i8 8, i8 0 } -@global_type_table = global [6 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const TypeInfo>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const *mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::<*mut core::void>"] +@global_type_table = global [5 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const TypeInfo>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const *mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::<*mut core::void>"] @dispatchTable = global %DispatchTable zeroinitializer @allocatorHandle = unnamed_addr global i8* null diff --git a/crates/mun_codegen/src/snapshots/test__field_expr_file_ir.snap b/crates/mun_codegen/src/snapshots/test__field_expr_file_ir.snap index 7ea561cb3..65991d1dc 100644 --- a/crates/mun_codegen/src/snapshots/test__field_expr_file_ir.snap +++ b/crates/mun_codegen/src/snapshots/test__field_expr_file_ir.snap @@ -5,14 +5,14 @@ expression: "struct(value) Bar(float, Foo);\nstruct(value) Foo { a: int };\n\nfn ; ModuleID = 'main.mun' source_filename = "main.mun" -%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i64 (%Foo)*, %Foo (%Bar)* } +%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i64 (%Foo)*, %Foo (%Bar)* } %Foo = type { i64 } %Bar = type { double, %Foo } %struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } @allocatorHandle = external global i8* @dispatchTable = external global %DispatchTable -@global_type_table = external global [8 x %struct.MunTypeInfo addrspace(4)*] +@global_type_table = external global [7 x %struct.MunTypeInfo addrspace(4)*] define double @bar_0(%Bar) { body: @@ -43,9 +43,9 @@ define i64 @bar_1_foo_a(%Bar) { body: %.fca.0.extract = extractvalue %Bar %0, 0 %.fca.1.0.extract = extractvalue %Bar %0, 1, 0 - %bar_1_ptr = load %Foo (%Bar)*, %Foo (%Bar)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 3) + %bar_1_ptr = load %Foo (%Bar)*, %Foo (%Bar)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 2) %bar_1 = call %Foo %bar_1_ptr(%Bar %0) - %foo_a_ptr = load i64 (%Foo)*, i64 (%Foo)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 2) + %foo_a_ptr = load i64 (%Foo)*, i64 (%Foo)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 1) %foo_a = call i64 %foo_a_ptr(%Foo %bar_1) ret i64 %foo_a } diff --git a/crates/mun_codegen/src/snapshots/test__field_expr_group_ir.snap b/crates/mun_codegen/src/snapshots/test__field_expr_group_ir.snap index 72d20f4bf..640945bc8 100644 --- a/crates/mun_codegen/src/snapshots/test__field_expr_group_ir.snap +++ b/crates/mun_codegen/src/snapshots/test__field_expr_group_ir.snap @@ -7,7 +7,7 @@ source_filename = "group_name" %struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } %struct.MunStructInfo = type { i8 addrspace(4)*, i8 addrspace(4)* addrspace(4)*, %struct.MunTypeInfo addrspace(4)* addrspace(4)*, i16 addrspace(4)*, i16, i8 } -%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i64 (%Foo)*, %Foo (%Bar)* } +%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i64 (%Foo)*, %Foo (%Bar)* } %Foo = type { i64 } %Bar = type { double, %Foo } @@ -34,12 +34,10 @@ source_filename = "group_name" @"struct_info::::field_types" = private unnamed_addr constant [2 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::"] @"struct_info::::field_offsets" = private unnamed_addr constant [2 x i16] [i16 0, i16 8] @"type_info::" = private unnamed_addr constant { %struct.MunTypeInfo, %struct.MunStructInfo } { %struct.MunTypeInfo { [16 x i8] c"\DD\C3_\88\FAq\B6\EF\14*\E6\1F56FS", [4 x i8]* @"type_info::::name", i32 128, i8 8, i8 1 }, %struct.MunStructInfo { [4 x i8]* @"struct_info::::name", [2 x i8 addrspace(4)*]* @1, [2 x %struct.MunTypeInfo addrspace(4)*]* @"struct_info::::field_types", [2 x i16]* @"struct_info::::field_offsets", i16 2, i8 1 } } -@"type_info::<*const core::void>::name" = private unnamed_addr constant [18 x i8] c"*const core::void\00" -@"type_info::<*const core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\EF\D3\E0ac~\5C\D4\EF\AE\B1}\CA\BE\DA\16", [18 x i8]* @"type_info::<*const core::void>::name", i32 64, i8 8, i8 0 } @"type_info::<*mut core::void>::name" = private unnamed_addr constant [16 x i8] c"*mut core::void\00" @"type_info::<*mut core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\F0Y\22\FC\95\9E\7F\CE\08T\B1\A2\CD\A7\FAz", [16 x i8]* @"type_info::<*mut core::void>::name", i32 64, i8 8, i8 0 } -@global_type_table = global [8 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const TypeInfo>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const *mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::<*mut core::void>"] -@dispatchTable = global %DispatchTable { i8* addrspace(4)* (i8 addrspace(4)*, i8*)* null, i8* addrspace(4)* (i8 addrspace(4)*, i8*)* null, i64 (%Foo)* @foo_a, %Foo (%Bar)* @bar_1 } +@global_type_table = global [7 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const TypeInfo>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const *mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*mut core::void>"] +@dispatchTable = global %DispatchTable { i8* addrspace(4)* (i8 addrspace(4)*, i8*)* null, i64 (%Foo)* @foo_a, %Foo (%Bar)* @bar_1 } @allocatorHandle = unnamed_addr global i8* null declare i64 @foo_a(%Foo) diff --git a/crates/mun_codegen/src/snapshots/test__gc_struct_file_ir.snap b/crates/mun_codegen/src/snapshots/test__gc_struct_file_ir.snap index d0538c05b..82c422fb1 100644 --- a/crates/mun_codegen/src/snapshots/test__gc_struct_file_ir.snap +++ b/crates/mun_codegen/src/snapshots/test__gc_struct_file_ir.snap @@ -5,20 +5,20 @@ expression: "struct(gc) Foo { a: int, b: int };\n\nfn foo() {\n let a = Foo { ; ModuleID = 'main.mun' source_filename = "main.mun" -%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } +%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } %struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } %Foo = type { i64, i64 } @allocatorHandle = external global i8* @dispatchTable = external global %DispatchTable -@global_type_table = external global [6 x %struct.MunTypeInfo addrspace(4)*] +@global_type_table = external global [5 x %struct.MunTypeInfo addrspace(4)*] define void @foo() { body: %b5 = alloca %Foo* addrspace(4)* %a = alloca %Foo* addrspace(4)* - %new_ptr = load i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 1) - %Foo_ptr = load %struct.MunTypeInfo addrspace(4)*, %struct.MunTypeInfo addrspace(4)** getelementptr inbounds ([6 x %struct.MunTypeInfo addrspace(4)*], [6 x %struct.MunTypeInfo addrspace(4)*]* @global_type_table, i32 0, i32 0) + %new_ptr = load i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 0) + %Foo_ptr = load %struct.MunTypeInfo addrspace(4)*, %struct.MunTypeInfo addrspace(4)** getelementptr inbounds ([5 x %struct.MunTypeInfo addrspace(4)*], [5 x %struct.MunTypeInfo addrspace(4)*]* @global_type_table, i32 0, i32 0) %type_info_ptr_to_i8_ptr = bitcast %struct.MunTypeInfo addrspace(4)* %Foo_ptr to i8 addrspace(4)* %allocator_handle = load i8*, i8** @allocatorHandle %new = call i8* addrspace(4)* %new_ptr(i8 addrspace(4)* %type_info_ptr_to_i8_ptr, i8* %allocator_handle) diff --git a/crates/mun_codegen/src/snapshots/test__gc_struct_group_ir.snap b/crates/mun_codegen/src/snapshots/test__gc_struct_group_ir.snap index d809106ab..c657a67fd 100644 --- a/crates/mun_codegen/src/snapshots/test__gc_struct_group_ir.snap +++ b/crates/mun_codegen/src/snapshots/test__gc_struct_group_ir.snap @@ -7,7 +7,7 @@ source_filename = "group_name" %struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } %struct.MunStructInfo = type { i8 addrspace(4)*, i8 addrspace(4)* addrspace(4)*, %struct.MunTypeInfo addrspace(4)* addrspace(4)*, i16 addrspace(4)*, i16, i8 } -%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } +%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } @"type_info::::name" = private unnamed_addr constant [4 x i8] c"Foo\00" @"struct_info::::name" = private unnamed_addr constant [4 x i8] c"Foo\00" @@ -23,11 +23,9 @@ source_filename = "group_name" @"type_info::<*const TypeInfo>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"=\A1-\1F\C2\A7\88`d\90\F4\B5\BEE}x", [16 x i8]* @"type_info::<*const TypeInfo>::name", i32 64, i8 8, i8 0 } @"type_info::<*const *mut core::void>::name" = private unnamed_addr constant [23 x i8] c"*const *mut core::void\00" @"type_info::<*const *mut core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\C5fO\BD\84\DF\06\BFd+\B1\9Abv\CE\00", [23 x i8]* @"type_info::<*const *mut core::void>::name", i32 64, i8 8, i8 0 } -@"type_info::<*const core::void>::name" = private unnamed_addr constant [18 x i8] c"*const core::void\00" -@"type_info::<*const core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\EF\D3\E0ac~\5C\D4\EF\AE\B1}\CA\BE\DA\16", [18 x i8]* @"type_info::<*const core::void>::name", i32 64, i8 8, i8 0 } @"type_info::<*mut core::void>::name" = private unnamed_addr constant [16 x i8] c"*mut core::void\00" @"type_info::<*mut core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\F0Y\22\FC\95\9E\7F\CE\08T\B1\A2\CD\A7\FAz", [16 x i8]* @"type_info::<*mut core::void>::name", i32 64, i8 8, i8 0 } -@global_type_table = global [6 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const TypeInfo>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const *mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::<*mut core::void>"] +@global_type_table = global [5 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const TypeInfo>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const *mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::<*mut core::void>"] @dispatchTable = global %DispatchTable zeroinitializer @allocatorHandle = unnamed_addr global i8* null diff --git a/crates/mun_codegen/src/snapshots/test__struct_test_file_ir.snap b/crates/mun_codegen/src/snapshots/test__struct_test_file_ir.snap index aef4ac5e1..e16e1e146 100644 --- a/crates/mun_codegen/src/snapshots/test__struct_test_file_ir.snap +++ b/crates/mun_codegen/src/snapshots/test__struct_test_file_ir.snap @@ -5,7 +5,7 @@ expression: "struct(value) Bar(float, int, bool, Foo);\nstruct(value) Foo { a: i ; ModuleID = 'main.mun' source_filename = "main.mun" -%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } +%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } %struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } %Baz = type {} %Bar = type { double, i64, i1, %Foo } @@ -13,7 +13,7 @@ source_filename = "main.mun" @allocatorHandle = external global i8* @dispatchTable = external global %DispatchTable -@global_type_table = external global [10 x %struct.MunTypeInfo addrspace(4)*] +@global_type_table = external global [9 x %struct.MunTypeInfo addrspace(4)*] define void @foo() { body: diff --git a/crates/mun_codegen/src/snapshots/test__struct_test_group_ir.snap b/crates/mun_codegen/src/snapshots/test__struct_test_group_ir.snap index e4050515c..5d1a20f77 100644 --- a/crates/mun_codegen/src/snapshots/test__struct_test_group_ir.snap +++ b/crates/mun_codegen/src/snapshots/test__struct_test_group_ir.snap @@ -7,7 +7,7 @@ source_filename = "group_name" %struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } %struct.MunStructInfo = type { i8 addrspace(4)*, i8 addrspace(4)* addrspace(4)*, %struct.MunTypeInfo addrspace(4)* addrspace(4)*, i16 addrspace(4)*, i16, i8 } -%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } +%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } @"type_info::::name" = private unnamed_addr constant [4 x i8] c"Foo\00" @"struct_info::::name" = private unnamed_addr constant [4 x i8] c"Foo\00" @@ -36,14 +36,12 @@ source_filename = "group_name" @"struct_info::::field_types" = private unnamed_addr constant [4 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::"] @"struct_info::::field_offsets" = private unnamed_addr constant [4 x i16] [i16 0, i16 8, i16 16, i16 24] @"type_info::" = private unnamed_addr constant { %struct.MunTypeInfo, %struct.MunStructInfo } { %struct.MunTypeInfo { [16 x i8] c"\DD\C3_\88\FAq\B6\EF\14*\E6\1F56FS", [4 x i8]* @"type_info::::name", i32 256, i8 8, i8 1 }, %struct.MunStructInfo { [4 x i8]* @"struct_info::::name", [4 x i8 addrspace(4)*]* @1, [4 x %struct.MunTypeInfo addrspace(4)*]* @"struct_info::::field_types", [4 x i16]* @"struct_info::::field_offsets", i16 4, i8 1 } } -@"type_info::<*const core::void>::name" = private unnamed_addr constant [18 x i8] c"*const core::void\00" -@"type_info::<*const core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\EF\D3\E0ac~\5C\D4\EF\AE\B1}\CA\BE\DA\16", [18 x i8]* @"type_info::<*const core::void>::name", i32 64, i8 8, i8 0 } @"type_info::<*mut core::void>::name" = private unnamed_addr constant [16 x i8] c"*mut core::void\00" @"type_info::<*mut core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\F0Y\22\FC\95\9E\7F\CE\08T\B1\A2\CD\A7\FAz", [16 x i8]* @"type_info::<*mut core::void>::name", i32 64, i8 8, i8 0 } @"type_info::::name" = private unnamed_addr constant [4 x i8] c"Baz\00" @"struct_info::::name" = private unnamed_addr constant [4 x i8] c"Baz\00" @"type_info::" = private unnamed_addr constant { %struct.MunTypeInfo, %struct.MunStructInfo } { %struct.MunTypeInfo { [16 x i8] c"\F8\DC\E6\7F,\948\82\82\ED?\A7\97\96\8A|", [4 x i8]* @"type_info::::name", i32 0, i8 1, i8 1 }, %struct.MunStructInfo { [4 x i8]* @"struct_info::::name", i8 addrspace(4)* addrspace(4)* null, %struct.MunTypeInfo addrspace(4)* addrspace(4)* null, i16 addrspace(4)* null, i16 0, i8 1 } } -@global_type_table = global [10 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const TypeInfo>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const *mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::<*mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::"] +@global_type_table = global [9 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const TypeInfo>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const *mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::"] @dispatchTable = global %DispatchTable zeroinitializer @allocatorHandle = unnamed_addr global i8* null diff --git a/crates/mun_gc/Cargo.toml b/crates/mun_gc/Cargo.toml new file mode 100644 index 000000000..64e9116d2 --- /dev/null +++ b/crates/mun_gc/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "mun_gc" +version = "0.1.0" +authors = ["The Mun Team "] +edition = "2018" +homepage = "https://mun-lang.org" +repository = "https://github.com/mun-lang/mun" +license = "MIT OR Apache-2.0" +description = "Garbage collection crate for Mun" + +[dependencies] +abi = { path = "../mun_abi", package = "mun_abi" } +parking_lot = "0.10" + +[dev-dependencies] +paste = "0.1" diff --git a/crates/mun_gc/src/lib.rs b/crates/mun_gc/src/lib.rs new file mode 100644 index 000000000..d0dad2b58 --- /dev/null +++ b/crates/mun_gc/src/lib.rs @@ -0,0 +1,88 @@ +mod mark_sweep; +mod ptr; +mod root_ptr; + +pub use mark_sweep::MarkSweep; +pub use ptr::{GcPtr, HasIndirectionPtr, RawGcPtr}; +pub use root_ptr::GcRootPtr; +use std::alloc::Layout; +use std::marker::PhantomData; + +/// Contains stats about the current state of a GC implementation +#[derive(Debug, Clone, Default)] +pub struct Stats { + pub allocated_memory: usize, +} + +/// A trait used by the `GcRuntime` to identify an object type. +pub trait Type: Send + Sync { + type Trace: Iterator; + + /// Returns the memory layout of this type. + fn layout(&self) -> Layout; + + /// Returns an iterator to iterate over all GC objects that are referenced by the given object. + fn trace(&self, obj: GcPtr) -> Self::Trace; +} + +/// An object that can be used to allocate and collect memory. +pub trait GcRuntime: Send + Sync { + /// Allocates an object of the given type returning a GcPtr + fn alloc(&self, ty: T) -> GcPtr; + + /// Returns the type of the specified `obj`. + fn ptr_type(&self, obj: GcPtr) -> T; + + /// Roots the specified `obj`, which keeps it and objects it references alive. Objects marked + /// as root, must call `unroot` before they can be collected. An object can be rooted multiple + /// times, but you must make sure to call `unroot` an equal number of times before the object + /// can be collected. + fn root(&self, obj: GcPtr); + + /// Unroots the specified `obj`, potentially allowing it and objects it references to be + /// collected. An object can be rooted multiple times, so you must make sure to call `unroot` + /// the same number of times as `root` was called before the object can be collected. + fn unroot(&self, obj: GcPtr); + + /// Returns stats about the current state of the runtime. + fn stats(&self) -> Stats; +} + +/// The `Observer` trait allows receiving of `Event`s. +pub trait Observer: Send + Sync { + type Event; + + fn event(&self, _event: Self::Event) {} +} + +/// An `Event` is an event that can be emitted by a `GcRuntime` through the use of an `Observer`. +/// This enables tracking of the runtimes behavior which is useful for testing. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Event { + /// The GC performed an allocation + Allocation(GcPtr), + + /// A GC cycle started + Start, + + /// A deallocation took place + Deallocation(GcPtr), + + /// A GC cycle ended + End, +} + +/// A default implementation of an `Observer` which ensures that the compiler does not generate +/// code for event handling. +#[derive(Clone)] +pub struct NoopObserver { + data: PhantomData, +} +impl Observer for NoopObserver { + type Event = T; +} +impl Default for NoopObserver { + fn default() -> Self { + NoopObserver { data: PhantomData } + } +} diff --git a/crates/mun_gc/src/mark_sweep.rs b/crates/mun_gc/src/mark_sweep.rs new file mode 100644 index 000000000..8bc7acbb2 --- /dev/null +++ b/crates/mun_gc/src/mark_sweep.rs @@ -0,0 +1,219 @@ +use crate::{Event, GcPtr, GcRuntime, Observer, RawGcPtr, Stats, Type}; +use parking_lot::RwLock; +use std::collections::{HashMap, VecDeque}; +use std::ops::Deref; +use std::pin::Pin; + +/// Implements a simple mark-sweep type garbage collector. +#[derive(Debug)] +pub struct MarkSweep> { + objects: RwLock>>>>, + observer: O, + stats: RwLock, +} + +impl + Default> Default for MarkSweep { + fn default() -> Self { + MarkSweep { + objects: RwLock::new(HashMap::new()), + observer: O::default(), + stats: RwLock::new(Stats::default()), + } + } +} + +impl> MarkSweep { + /// Creates a `MarkSweep` memory collector with the specified `Observer`. + pub fn with_observer(observer: O) -> Self { + Self { + objects: RwLock::new(HashMap::new()), + observer, + stats: RwLock::new(Stats::default()), + } + } + + /// Returns the observer + pub fn observer(&self) -> &O { + &self.observer + } +} + +impl> GcRuntime for MarkSweep { + fn alloc(&self, ty: T) -> GcPtr { + let layout = ty.layout(); + let ptr = unsafe { std::alloc::alloc(layout) }; + let object = Box::pin(ObjectInfo { + ptr, + ty, + roots: 0, + color: Color::White, + }); + + // We want to return a pointer to the `ObjectInfo`, to be used as handle. + let handle = (object.as_ref().deref() as *const _ as RawGcPtr).into(); + + { + let mut objects = self.objects.write(); + objects.insert(handle, object); + } + + { + let mut stats = self.stats.write(); + stats.allocated_memory += layout.size(); + } + + self.observer.event(Event::Allocation(handle)); + handle + } + + fn ptr_type(&self, handle: GcPtr) -> T { + let _ = self.objects.read(); + + // Convert the handle to our internal representation + let object_info: *const ObjectInfo = handle.into(); + + // Return the type of the object + unsafe { (*object_info).ty.clone() } + } + + fn root(&self, handle: GcPtr) { + let _ = self.objects.write(); + + // Convert the handle to our internal representation + let object_info: *mut ObjectInfo = handle.into(); + + unsafe { (*object_info).roots += 1 }; + } + + fn unroot(&self, handle: GcPtr) { + let _ = self.objects.write(); + + // Convert the handle to our internal representation + let object_info: *mut ObjectInfo = handle.into(); + + unsafe { (*object_info).roots -= 1 }; + } + + fn stats(&self) -> Stats { + self.stats.read().clone() + } +} + +impl + Default> MarkSweep { + /// Collects all memory that is no longer referenced by rooted objects. Returns `true` if memory + /// was reclaimed, `false` otherwise. + pub fn collect(&self) -> bool { + self.observer.event(Event::Start); + + let mut objects = self.objects.write(); + + // Get all roots + let mut roots = objects + .iter() + .filter_map(|(_, obj)| { + if obj.roots > 0 { + Some(obj.as_ref().get_ref() as *const _ as *mut ObjectInfo) + } else { + None + } + }) + .collect::>(); + + // Iterate over all roots + while let Some(next) = roots.pop_front() { + let handle = (next as *const _ as RawGcPtr).into(); + + // Trace all other objects + for reference in unsafe { (*next).ty.trace(handle) } { + let ref_ptr = objects + .get_mut(&reference) + .expect("found invalid reference"); + if ref_ptr.color == Color::White { + let ptr = ref_ptr.as_ref().get_ref() as *const _ as *mut ObjectInfo; + unsafe { (*ptr).color = Color::Gray }; + roots.push_back(ptr); + } + } + + // This object has been traced + unsafe { + (*next).color = Color::Black; + } + } + + // Sweep all non-reachable objects + let size_before = objects.len(); + objects.retain(|h, obj| { + if obj.color == Color::Black { + unsafe { + obj.as_mut().get_unchecked_mut().color = Color::White; + } + true + } else { + self.observer.event(Event::Deallocation(*h)); + { + let mut stats = self.stats.write(); + stats.allocated_memory -= obj.ty.layout().size(); + } + false + } + }); + let size_after = objects.len(); + + self.observer.event(Event::End); + + size_before != size_after + } +} + +/// Coloring used in the Mark Sweep phase. +#[derive(Debug, PartialEq, Eq)] +enum Color { + /// A white object has not been seen yet by the mark phase + White, + + /// A gray object has been seen by the mark phase but has not yet been visited + Gray, + + /// A black object has been visited by the mark phase + Black, +} + +/// An indirection table that stores the address to the actual memory, the type of the object and +/// meta information. +#[derive(Debug)] +#[repr(C)] +struct ObjectInfo { + pub ptr: *mut u8, + pub roots: u32, + pub color: Color, + pub ty: T, +} + +/// An `ObjectInfo` is thread-safe. +unsafe impl Send for ObjectInfo {} +unsafe impl Sync for ObjectInfo {} + +impl Into<*const ObjectInfo> for GcPtr { + fn into(self) -> *const ObjectInfo { + self.as_ptr() as *const ObjectInfo + } +} + +impl Into<*mut ObjectInfo> for GcPtr { + fn into(self) -> *mut ObjectInfo { + self.as_ptr() as *mut ObjectInfo + } +} + +impl Into for *const ObjectInfo { + fn into(self) -> GcPtr { + (self as RawGcPtr).into() + } +} + +impl Into for *mut ObjectInfo { + fn into(self) -> GcPtr { + (self as RawGcPtr).into() + } +} diff --git a/crates/mun_gc/src/ptr.rs b/crates/mun_gc/src/ptr.rs new file mode 100644 index 000000000..d2fdc562e --- /dev/null +++ b/crates/mun_gc/src/ptr.rs @@ -0,0 +1,59 @@ +/// A `GcPtr` is what you interact with outside of the allocator. It is a pointer to a piece of +/// memory that points to the actual data stored in memory. +/// +/// This creates an indirection that must be followed to get to the actual data of the object. Note +/// that the `GcPtr` must therefore be pinned in memory whereas the contained memory pointer may +/// change. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[repr(transparent)] +pub struct GcPtr(RawGcPtr); + +/// A `GcPtr` is thread safe. +unsafe impl Send for GcPtr {} +unsafe impl Sync for GcPtr {} + +/// A `RawGcPtr` is an unsafe version of a `GcPtr`. It represents the raw internal pointer +/// semantics used by the runtime. +pub type RawGcPtr = *const *mut std::ffi::c_void; + +pub trait HasIndirectionPtr { + /// Returns a pointer to the referenced memory. + /// + /// # Safety + /// + /// This is an unsafe method because derefencing could result in an access violation. + unsafe fn deref(&self) -> *const T; + + /// Returns a mutable pointer to the referenced memory. + /// + /// # Safety + /// + /// This is an unsafe method because derefencing could result in an access violation. + unsafe fn deref_mut(&mut self) -> *mut T { + self.deref::() as *mut _ + } +} + +impl HasIndirectionPtr for GcPtr { + unsafe fn deref(&self) -> *const T { + (*self.0).cast() + } +} + +impl Into for GcPtr { + fn into(self) -> RawGcPtr { + self.0 + } +} + +impl Into for RawGcPtr { + fn into(self) -> GcPtr { + GcPtr(self) + } +} + +impl GcPtr { + pub(crate) fn as_ptr(self) -> RawGcPtr { + self.0 + } +} diff --git a/crates/mun_gc/src/root_ptr.rs b/crates/mun_gc/src/root_ptr.rs new file mode 100644 index 000000000..fd0d9cd40 --- /dev/null +++ b/crates/mun_gc/src/root_ptr.rs @@ -0,0 +1,65 @@ +use crate::{GcPtr, GcRuntime, HasIndirectionPtr, Type}; +use std::marker::PhantomData; +use std::sync::{Arc, Weak}; + +/// A `GcPtr` that automatically roots and unroots its internal `GcPtr`. +pub struct GcRootPtr> { + handle: GcPtr, + runtime: Weak, + ty: PhantomData, +} + +impl> Clone for GcRootPtr { + fn clone(&self) -> Self { + if let Some(runtime) = self.runtime.upgrade() { + runtime.root(self.handle) + } + Self { + handle: self.handle, + runtime: self.runtime.clone(), + ty: PhantomData, + } + } +} + +impl> GcRootPtr { + /// Constructs a new GCRootHandle from a runtime and a handle + pub fn new(runtime: &Arc, handle: GcPtr) -> Self { + runtime.root(handle); + Self { + handle, + runtime: Arc::downgrade(runtime), + ty: PhantomData, + } + } + + /// Returns the handle of this instance + pub fn handle(&self) -> GcPtr { + self.handle + } + + /// Unroots the handle consuming self and returning the unrooted handle + pub fn unroot(self) -> GcPtr { + self.handle + } +} + +impl> Into for GcRootPtr { + fn into(self) -> GcPtr { + self.handle + } +} + +impl> Drop for GcRootPtr { + fn drop(&mut self) { + if let Some(runtime) = self.runtime.upgrade() { + runtime.unroot(self.handle) + } + } +} + +impl> HasIndirectionPtr for GcRootPtr { + unsafe fn deref(&self) -> *const R { + self.handle.deref() + } +} diff --git a/crates/mun_gc/tests/alloc.rs b/crates/mun_gc/tests/alloc.rs new file mode 100644 index 000000000..31921a3c2 --- /dev/null +++ b/crates/mun_gc/tests/alloc.rs @@ -0,0 +1,68 @@ +#[macro_use] +mod util; + +use mun_gc::{Event, GcRootPtr, GcRuntime, MarkSweep}; +use std::sync::Arc; +use util::{EventAggregator, HasTypeInfo, TypeInfo}; + +#[test] +fn alloc() { + let runtime = MarkSweep::<&'static TypeInfo, EventAggregator>::default(); + let handle = runtime.alloc(i64::type_info()); + + assert!(std::ptr::eq(runtime.ptr_type(handle), i64::type_info())); + + let mut events = runtime.observer().take_all().into_iter(); + assert_eq!(events.next(), Some(Event::Allocation(handle))); + assert_eq!(events.next(), None); +} + +#[test] +fn collect_simple() { + let runtime = MarkSweep::<&'static TypeInfo, EventAggregator>::default(); + let handle = runtime.alloc(i64::type_info()); + + runtime.collect(); + + let mut events = runtime.observer().take_all().into_iter(); + assert_eq!(events.next(), Some(Event::Allocation(handle))); + assert_eq!(events.next(), Some(Event::Start)); + assert_eq!(events.next(), Some(Event::Deallocation(handle))); + assert_eq!(events.next(), Some(Event::End)); + assert_eq!(events.next(), None); +} + +#[test] +fn collect_rooted() { + let runtime = Arc::new(MarkSweep::<&'static TypeInfo, EventAggregator>::default()); + + // Allocate simple object and rooted object + let handle = runtime.alloc(i64::type_info()); + let rooted = GcRootPtr::new(&runtime, runtime.alloc(i64::type_info())); + + // Collect unreachable objects, should not collect the root handle + runtime.collect(); + + // Performing a collection cycle now should not do a thing + runtime.collect(); + + // Drop the rooted handle which should become collectable now + let rooted_handle = rooted.unroot(); + + // Collect unreachable objects, should now collect the rooted handle + runtime.collect(); + + // See if our version of events matched + let mut events = runtime.observer().take_all().into_iter(); + assert_eq!(events.next(), Some(Event::Allocation(handle))); + assert_eq!(events.next(), Some(Event::Allocation(rooted_handle))); + assert_eq!(events.next(), Some(Event::Start)); + assert_eq!(events.next(), Some(Event::Deallocation(handle))); + assert_eq!(events.next(), Some(Event::End)); + assert_eq!(events.next(), Some(Event::Start)); + assert_eq!(events.next(), Some(Event::End)); + assert_eq!(events.next(), Some(Event::Start)); + assert_eq!(events.next(), Some(Event::Deallocation(rooted_handle))); + assert_eq!(events.next(), Some(Event::End)); + assert_eq!(events.next(), None); +} diff --git a/crates/mun_gc/tests/struct.rs b/crates/mun_gc/tests/struct.rs new file mode 100644 index 000000000..ae2c0f628 --- /dev/null +++ b/crates/mun_gc/tests/struct.rs @@ -0,0 +1,97 @@ +#[macro_use] +mod util; + +use mun_gc::{Event, GcPtr, GcRootPtr, GcRuntime, HasIndirectionPtr, MarkSweep, Type}; +use std::sync::Arc; +use util::{EventAggregator, HasTypeInfo, Trace, TypeInfo}; + +struct Foo { + bar: GcPtr, +} + +impl Trace for Foo { + fn trace(&self, handles: &mut Vec) { + handles.push(self.bar) + } +} + +impl_struct_ty!(Foo); + +#[test] +fn test_trace() { + let runtime = MarkSweep::<&'static TypeInfo, EventAggregator>::default(); + let mut foo_handle = runtime.alloc(Foo::type_info()); + let bar_handle = runtime.alloc(i64::type_info()); + + // Assign bar to foo.bar + unsafe { + (*foo_handle.deref_mut::()).bar = bar_handle; + } + + // Trace foo to see if we get bar back + let mut trace = Foo::type_info().trace(foo_handle); + + assert_eq!(trace.next(), Some(bar_handle)); + assert_eq!(trace.next(), None) +} + +#[test] +fn trace_collect() { + let runtime = Arc::new(MarkSweep::<&'static TypeInfo, EventAggregator>::default()); + let mut foo = GcRootPtr::new(&runtime, runtime.alloc(Foo::type_info())); + let bar = runtime.alloc(i64::type_info()); + + // Assign bar to foo.bar + unsafe { + (*foo.deref_mut::()).bar = bar; + } + + // Collect garbage, bar should not be collected + runtime.collect(); + + // Drop foo + let foo = foo.unroot(); + + // Collect garbage, both foo and bar should be collected + runtime.collect(); + + let mut events = runtime.observer().take_all().into_iter(); + assert_eq!(events.next(), Some(Event::Allocation(foo))); + assert_eq!(events.next(), Some(Event::Allocation(bar))); + assert_eq!(events.next(), Some(Event::Start)); + assert_eq!(events.next(), Some(Event::End)); + assert_eq!(events.next(), Some(Event::Start)); + assert_variant!(events.next(), Some(Event::Deallocation(..))); // Don't care about the order + assert_variant!(events.next(), Some(Event::Deallocation(..))); + assert_eq!(events.next(), Some(Event::End)); + assert_eq!(events.next(), None); +} + +#[test] +fn trace_cycle() { + let runtime = Arc::new(MarkSweep::<&'static TypeInfo, EventAggregator>::default()); + let mut foo = GcRootPtr::new(&runtime, runtime.alloc(Foo::type_info())); + + // Assign bar to foo.bar + unsafe { + (*foo.deref_mut::()).bar = foo.handle(); + } + + // Collect garbage, nothing should be collected since foo is rooted + runtime.collect(); + + // Drop foo + let foo = foo.unroot(); + + // Collect garbage, foo should be collected + runtime.collect(); + + let mut events = runtime.observer().take_all().into_iter(); + assert_eq!(events.next(), Some(Event::Allocation(foo))); + assert_eq!(events.next(), Some(Event::Start)); + assert_eq!(events.next(), Some(Event::End)); + assert_eq!(events.next(), Some(Event::Start)); + assert_eq!(events.next(), Some(Event::Deallocation(foo))); + assert_eq!(events.next(), Some(Event::End)); + assert_eq!(events.next(), None); +} diff --git a/crates/mun_gc/tests/util/mod.rs b/crates/mun_gc/tests/util/mod.rs new file mode 100644 index 000000000..cb48b8e94 --- /dev/null +++ b/crates/mun_gc/tests/util/mod.rs @@ -0,0 +1,133 @@ +#![allow(dead_code, unused_macros)] + +use mun_gc::GcPtr; +use parking_lot::Mutex; +use std::alloc::Layout; + +pub struct TypeInfo { + pub size: usize, + pub alignment: usize, + pub tracer: Option<&'static fn(handle: GcPtr) -> Vec>, +} + +pub trait Trace { + /// Called to collect all GC handles in the type + fn trace(&self, handles: &mut Vec); +} + +pub trait HasTypeInfo { + fn type_info() -> &'static TypeInfo; +} + +macro_rules! impl_primitive_types { + ($( + $ty:ident + ),+) => { + $( + paste::item! { + #[allow(non_upper_case_globals)] + static []: TypeInfo = TypeInfo { + size: std::mem::size_of::<$ty>(), + alignment: std::mem::align_of::<$ty>(), + tracer: None + }; + + impl HasTypeInfo for $ty { + fn type_info() -> &'static TypeInfo { + &[] + } + } + } + )+ + } +} + +macro_rules! impl_struct_ty { + ($ty:ident) => { + paste::item! { + #[allow(non_upper_case_globals, non_snake_case)] + fn [](obj:GcPtr) -> Vec { + let mut result = Vec::new(); + let foo = unsafe { &(*obj.deref::<$ty>()) }; + foo.trace(&mut result); + result + } + + #[allow(non_upper_case_globals)] + static []: TypeInfo = TypeInfo { + size: std::mem::size_of::<$ty>(), + alignment: std::mem::align_of::<$ty>(), + tracer: Some(&([] as fn(handle: GcPtr) -> Vec)) + }; + + impl HasTypeInfo for $ty { + fn type_info() -> &'static TypeInfo { + &[] + } + } + } + }; +} + +impl_primitive_types!(i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, bool); + +impl mun_gc::Type for &'static TypeInfo { + type Trace = as IntoIterator>::IntoIter; + + fn layout(&self) -> Layout { + Layout::from_size_align(self.size as usize, self.alignment as usize) + .expect("invalid layout specified by TypeInfo") + } + + fn trace(&self, obj: GcPtr) -> Self::Trace { + let handles = if let Some(tracer) = self.tracer { + tracer(obj) + } else { + Vec::new() + }; + handles.into_iter() + } +} + +pub struct EventAggregator { + events: Mutex>, +} + +impl Default for EventAggregator { + fn default() -> Self { + EventAggregator { + events: Mutex::new(Vec::new()), + } + } +} + +impl EventAggregator { + pub fn take_all(&self) -> Vec { + self.events.lock().drain(..).collect() + } +} + +impl mun_gc::Observer for EventAggregator { + type Event = T; + + fn event(&self, event: T) { + self.events.lock().push(event) + } +} + +macro_rules! assert_variant { + ($value:expr, $pattern:pat) => {{ + let value = &$value; + + if let $pattern = value { + } else { + panic!( + r#"assertion failed (value doesn't match pattern): + value: `{:?}`, + pattern: `{}`"#, + value, + stringify!($pattern) + ) + } + }}; // TODO: Additional patterns for trailing args, like assert and assert_eq +} diff --git a/crates/mun_runtime/Cargo.toml b/crates/mun_runtime/Cargo.toml index 9ac45ce87..1b3f1cc1b 100644 --- a/crates/mun_runtime/Cargo.toml +++ b/crates/mun_runtime/Cargo.toml @@ -10,6 +10,7 @@ description = "A runtime for hot reloading and invoking Mun from Rust" [dependencies] abi = { path = "../mun_abi", package = "mun_abi" } +gc = { path = "../mun_gc", package = "mun_gc" } failure = "0.1.5" libloading = "0.5" md5= "0.7.0" diff --git a/crates/mun_runtime/src/allocator.rs b/crates/mun_runtime/src/allocator.rs deleted file mode 100644 index ae3261c87..000000000 --- a/crates/mun_runtime/src/allocator.rs +++ /dev/null @@ -1,86 +0,0 @@ -use parking_lot::RwLock; -use std::alloc::Layout; -use std::collections::HashMap; -use std::ops::Deref; -use std::{pin::Pin, ptr}; - -#[derive(Debug)] -#[repr(C)] -pub struct ObjectInfo { - pub ptr: *mut u8, - pub type_info: *const abi::TypeInfo, -} - -pub type ObjectHandle = *const ObjectInfo; - -/// Provides allocator capabilities for a runtime. -#[derive(Debug, Default)] -pub struct Allocator { - objects: RwLock>>>, -} - -impl Allocator { - /// Allocates a new instance of an Allocator - pub fn new() -> Self { - Default::default() - } - - /// Allocates a block of memory - fn alloc(&self, size: usize, alignment: usize) -> *mut u8 { - unsafe { std::alloc::alloc(Layout::from_size_align_unchecked(size, alignment)) } - } - - /// Allocates a managed object of the specified type. - /// - /// # Safety - /// - /// `type_info` must be a valid pointer and remain valid throughout the lifetime of the created - /// object. - pub(crate) unsafe fn create_object(&self, type_info: *const abi::TypeInfo) -> ObjectHandle { - let type_info = type_info.as_ref().unwrap(); - - let ptr = self.alloc(type_info.size_in_bytes(), type_info.alignment()); - let object = Box::pin(ObjectInfo { ptr, type_info }); - - // We want to return a pointer to the `ObjectInfo`, to be used as handle. - let handle = object.as_ref().deref() as *const _ as ObjectHandle; - - let mut objects = self.objects.write(); - objects.insert(handle, object); - - handle - } - - /// Creates a shallow clone of the `src` object at a newly allocated memory location. - /// - /// # Safety - /// - /// `src` must be a valid pointer. - pub(crate) unsafe fn clone_object(&self, src: ObjectHandle) -> ObjectHandle { - let clone = { - let objects = self.objects.read(); - let src = objects - .get(&src) - .unwrap_or_else(|| panic!("Object with handle '{:?}' does not exist.", src)); - - let type_info = src.type_info.as_ref().unwrap(); - - let size = type_info.size_in_bytes(); - let dest = self.alloc(size, type_info.alignment()); - ptr::copy_nonoverlapping(src.ptr, dest, size as usize); - - Box::pin(ObjectInfo { - ptr: dest, - type_info, - }) - }; - - // We want to return a pointer to the `ObjectInfo`, to be used as handle. - let handle = clone.as_ref().deref() as *const _ as ObjectHandle; - - let mut objects = self.objects.write(); - objects.insert(handle, clone); - - handle - } -} diff --git a/crates/mun_runtime/src/assembly.rs b/crates/mun_runtime/src/assembly.rs index 783d56d37..ee8d81e7d 100644 --- a/crates/mun_runtime/src/assembly.rs +++ b/crates/mun_runtime/src/assembly.rs @@ -1,7 +1,7 @@ use std::io; use std::path::{Path, PathBuf}; -use crate::{Allocator, DispatchTable}; +use crate::DispatchTable; use abi::AssemblyInfo; use failure::Error; use libloading::Symbol; @@ -9,6 +9,7 @@ use libloading::Symbol; mod temp_library; use self::temp_library::TempLibrary; +use crate::garbage_collector::GarbageCollector; use std::sync::Arc; /// An assembly is a hot reloadable compilation unit, consisting of one or more Mun modules. @@ -16,7 +17,7 @@ pub struct Assembly { library_path: PathBuf, library: Option, info: AssemblyInfo, - allocator: Arc, + allocator: Arc, } impl Assembly { @@ -24,7 +25,7 @@ impl Assembly { pub fn load( library_path: &Path, runtime_dispatch_table: &mut DispatchTable, - allocator: Arc, + gc: Arc, ) -> Result { let library = TempLibrary::new(library_path)?; @@ -35,7 +36,7 @@ impl Assembly { let set_allocator_handle: Symbol<'_, extern "C" fn(*mut std::ffi::c_void)> = unsafe { library.library().get(b"set_allocator_handle") }?; - let allocator_ptr = Arc::into_raw(allocator.clone()) as *mut std::ffi::c_void; + let allocator_ptr = Arc::into_raw(gc.clone()) as *mut std::ffi::c_void; set_allocator_handle(allocator_ptr); let info = get_info(); @@ -48,7 +49,7 @@ impl Assembly { library_path: library_path.to_path_buf(), library: Some(library), info, - allocator, + allocator: gc, }) } diff --git a/crates/mun_runtime/src/garbage_collector.rs b/crates/mun_runtime/src/garbage_collector.rs new file mode 100644 index 000000000..ff7d0446a --- /dev/null +++ b/crates/mun_runtime/src/garbage_collector.rs @@ -0,0 +1,69 @@ +use gc::HasIndirectionPtr; +use std::alloc::Layout; + +#[derive(Clone, Debug)] +#[repr(transparent)] +pub struct RawTypeInfo(*const abi::TypeInfo); + +impl Into for *const abi::TypeInfo { + fn into(self) -> RawTypeInfo { + RawTypeInfo(self) + } +} + +unsafe impl Send for RawTypeInfo {} +unsafe impl Sync for RawTypeInfo {} + +pub struct Trace { + obj: GcPtr, + ty: RawTypeInfo, + index: usize, +} + +impl Iterator for Trace { + type Item = GcPtr; + + fn next(&mut self) -> Option { + let struct_ty = unsafe { self.ty.0.as_ref() }.unwrap().as_struct()?; + let field_count = struct_ty.field_types().len(); + while self.index < field_count { + let index = self.index; + self.index += 1; + + let field_ty = struct_ty.field_types()[index]; + if let Some(field_struct_ty) = field_ty.as_struct() { + if field_struct_ty.memory_kind == abi::StructMemoryKind::GC { + let offset = struct_ty.field_offsets()[index]; + return Some(unsafe { + *self.obj.deref::().add(offset as usize).cast::() + }); + } + } + } + None + } +} + +impl gc::Type for RawTypeInfo { + type Trace = Trace; + + fn layout(&self) -> Layout { + let ty = unsafe { &*self.0 }; + Layout::from_size_align(ty.size_in_bytes(), ty.alignment()) + .expect("invalid layout from Mun Type") + } + + fn trace(&self, obj: GcPtr) -> Self::Trace { + Trace { + ty: self.clone(), + obj, + index: 0, + } + } +} + +/// Defines the garbage collector used by the `Runtime`. +pub type GarbageCollector = gc::MarkSweep>; + +pub use gc::GcPtr; +pub type GcRootPtr = gc::GcRootPtr; diff --git a/crates/mun_runtime/src/lib.rs b/crates/mun_runtime/src/lib.rs index d4e83ef31..5f8d0c92b 100644 --- a/crates/mun_runtime/src/lib.rs +++ b/crates/mun_runtime/src/lib.rs @@ -8,7 +8,7 @@ mod assembly; mod function; #[macro_use] mod macros; -mod allocator; +mod garbage_collector; mod marshal; mod reflection; mod static_type_map; @@ -35,10 +35,11 @@ use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher}; pub use crate::marshal::Marshal; pub use crate::reflection::{ArgumentReflection, ReturnTypeReflection}; -pub use crate::allocator::Allocator; pub use crate::assembly::Assembly; use crate::function::IntoFunctionInfo; pub use crate::r#struct::StructRef; +use garbage_collector::GarbageCollector; +use gc::GcRuntime; use std::sync::Arc; impl_has_type_info_name!( @@ -76,11 +77,6 @@ impl RuntimeBuilder { new as extern "C" fn(*const abi::TypeInfo, *mut ffi::c_void) -> *const *mut ffi::c_void, ); - result.insert_fn( - "clone", - clone as extern "C" fn(*const ffi::c_void, *mut ffi::c_void) -> *const *mut ffi::c_void, - ); - result } @@ -140,7 +136,7 @@ pub struct Runtime { dispatch_table: DispatchTable, watcher: RecommendedWatcher, watcher_rx: Receiver, - allocator: Arc, + gc: Arc, _user_functions: Vec, } @@ -150,8 +146,8 @@ pub struct Runtime { /// /// The allocator must have been set using the `set_allocator_handle` call - exposed by the Mun /// library. -unsafe fn get_allocator(alloc_handle: *mut ffi::c_void) -> Arc { - Arc::from_raw(alloc_handle as *const Allocator) +unsafe fn get_allocator(alloc_handle: *mut ffi::c_void) -> Arc { + Arc::from_raw(alloc_handle as *const GarbageCollector) } extern "C" fn new( @@ -159,25 +155,12 @@ extern "C" fn new( alloc_handle: *mut ffi::c_void, ) -> *const *mut ffi::c_void { let allocator = unsafe { get_allocator(alloc_handle) }; - let handle = unsafe { allocator.create_object(type_info) as *const _ }; + let handle = allocator.alloc(type_info.into()); - // Prevent destruction + // Prevent destruction of the allocator mem::forget(allocator); - handle -} - -extern "C" fn clone( - src: *const ffi::c_void, - alloc_handle: *mut ffi::c_void, -) -> *const *mut ffi::c_void { - let allocator = unsafe { get_allocator(alloc_handle) }; - let handle = unsafe { allocator.clone_object(src as *const _) as *const _ }; - - // Prevent destruction - mem::forget(allocator); - - handle + handle.into() } impl Runtime { @@ -201,7 +184,7 @@ impl Runtime { dispatch_table, watcher, watcher_rx: rx, - allocator: Arc::new(Allocator::new()), + gc: Arc::new(self::garbage_collector::GarbageCollector::default()), _user_functions: storages, }; @@ -220,11 +203,8 @@ impl Runtime { .into()); } - let mut assembly = Assembly::load( - &library_path, - &mut self.dispatch_table, - self.allocator.clone(), - )?; + let mut assembly = + Assembly::load(&library_path, &mut self.dispatch_table, self.gc.clone())?; for dependency in assembly.info().dependencies() { self.add_assembly(Path::new(dependency))?; } @@ -242,11 +222,6 @@ impl Runtime { self.dispatch_table.get_fn(function_name) } - /// Returns the runtime's allocator. - pub fn get_allocator(&self) -> Arc { - self.allocator.clone() - } - /// Updates the state of the runtime. This includes checking for file changes, and reloading /// compiled assemblies. pub fn update(&mut self) -> bool { @@ -271,6 +246,22 @@ impl Runtime { } false } + + /// Returns the runtime's garbage collector. + pub(crate) fn gc(&self) -> &Arc { + &self.gc + } + + /// Collects all memory that is no longer referenced by rooted objects. Returns `true` if memory + /// was reclaimed, `false` otherwise. This behavior will likely change in the future. + pub fn gc_collect(&self) -> bool { + self.gc.collect() + } + + /// Returns statistics about the garbage collector. + pub fn gc_stats(&self) -> gc::Stats { + self.gc.stats() + } } /// Extends a result object with functions that allow retrying of an action. diff --git a/crates/mun_runtime/src/struct.rs b/crates/mun_runtime/src/struct.rs index 21064812a..bd25fc7e7 100644 --- a/crates/mun_runtime/src/struct.rs +++ b/crates/mun_runtime/src/struct.rs @@ -1,27 +1,27 @@ +use crate::garbage_collector::{GcPtr, GcRootPtr}; use crate::{ - allocator::ObjectHandle, marshal::Marshal, reflection::{ equals_argument_type, equals_return_type, ArgumentReflection, ReturnTypeReflection, }, Runtime, }; +use gc::{GcRuntime, HasIndirectionPtr}; use std::cell::RefCell; -use std::ffi; use std::ptr::{self, NonNull}; use std::rc::Rc; -use std::sync::Arc; /// Represents a Mun struct pointer. /// /// A byte pointer is used to make pointer arithmetic easier. #[repr(transparent)] #[derive(Clone)] -pub struct RawStruct(ObjectHandle); +pub struct RawStruct(GcPtr); impl RawStruct { - pub fn get_ptr(&self) -> *mut u8 { - unsafe { self.0.as_ref().unwrap() }.ptr + /// Returns a pointer to the struct memory. + pub unsafe fn get_ptr(&self) -> *const u8 { + self.0.deref() } } @@ -29,7 +29,8 @@ impl RawStruct { /// TODO: Handle destruction of `struct(value)` pub struct StructRef { runtime: Rc>, - raw: RawStruct, + handle: GcRootPtr, + type_info: *const abi::TypeInfo, info: abi::StructInfo, } @@ -40,16 +41,21 @@ impl StructRef { fn new(runtime: Rc>, type_info: &abi::TypeInfo, raw: RawStruct) -> StructRef { assert!(type_info.group.is_struct()); + let handle = { + let runtime_ref = runtime.borrow(); + GcRootPtr::new(runtime_ref.gc(), raw.0) + }; Self { runtime, - raw, + handle, + type_info: type_info as *const abi::TypeInfo, info: type_info.as_struct().unwrap().clone(), } } /// Consumes the `Struct`, returning a raw Mun struct. pub fn into_raw(self) -> RawStruct { - self.raw + RawStruct(self.handle.handle()) } /// Retrieves its struct information. @@ -57,6 +63,15 @@ impl StructRef { &self.info } + /// Returns the type information of the struct + /// + /// # Safety + /// + /// This method is unsafe because it returns a pointer that may point to deallocated memory. + pub unsafe fn type_info(&self) -> *const abi::TypeInfo { + self.type_info + } + /// /// /// # Safety @@ -65,7 +80,7 @@ impl StructRef { unsafe fn offset_unchecked(&self, field_idx: usize) -> NonNull { let offset = *self.info.field_offsets().get_unchecked(field_idx); // self.raw is never null - NonNull::new_unchecked(self.raw.get_ptr().add(offset as usize).cast::()) + NonNull::new_unchecked(self.handle.deref::().add(offset as usize).cast::() as *mut _) } /// Retrieves the value of the field corresponding to the specified `field_name`. @@ -144,7 +159,7 @@ impl ArgumentReflection for StructRef { } fn marshal(self) -> Self::Marshalled { - self.raw + self.into_raw() } } @@ -173,32 +188,35 @@ impl Marshal for RawStruct { ) -> StructRef { // `type_info` is only `None` for the `()` type let type_info = type_info.unwrap(); - let struct_info = type_info.as_struct().unwrap(); - let alloc_handle = Arc::into_raw(runtime.borrow().get_allocator()) as *mut std::ffi::c_void; - let object_handle = if struct_info.memory_kind == abi::StructMemoryKind::Value { + + // HACK: This is very hacky since we know nothing about the lifetime of abi::TypeInfo. + let type_info_ptr = (type_info as *const abi::TypeInfo).into(); + + // Copy the contents of the struct based on what kind of pointer we are dealing with + let gc_handle = if struct_info.memory_kind == abi::StructMemoryKind::Value { + // For a value struct, `ptr` points to a struct value. + // Create a new object using the runtime's intrinsic - let object_ptr: *const *mut ffi::c_void = - invoke_fn!(runtime.clone(), "new", type_info as *const _, alloc_handle).unwrap(); - let handle = object_ptr as ObjectHandle; + let mut gc_handle = { + let runtime_ref = runtime.borrow(); + runtime_ref.gc().alloc(type_info_ptr) + }; + // Construct let src = ptr.cast::().as_ptr() as *const _; - let dest = unsafe { handle.as_ref() }.unwrap().ptr; + let dest = unsafe { gc_handle.deref_mut::() }; let size = type_info.size_in_bytes(); unsafe { ptr::copy_nonoverlapping(src, dest, size as usize) }; - handle + gc_handle } else { - let ptr = unsafe { ptr.as_ref() }.0 as *const ffi::c_void; - - // Clone the struct using the runtime's intrinsic - let cloned_ptr: *const *mut ffi::c_void = - invoke_fn!(runtime.clone(), "clone", ptr, alloc_handle).unwrap(); + // For a gc struct, `ptr` points to a `GcPtr`. - cloned_ptr as ObjectHandle + unsafe { *ptr.cast::().as_ptr() } }; - StructRef::new(runtime, type_info, RawStruct(object_handle)) + StructRef::new(runtime, type_info, RawStruct(gc_handle)) } fn marshal_to_ptr(value: RawStruct, mut ptr: NonNull, type_info: Option<&abi::TypeInfo>) { diff --git a/crates/mun_runtime/src/test.rs b/crates/mun_runtime/src/test.rs index ed61177c4..560c108c9 100644 --- a/crates/mun_runtime/src/test.rs +++ b/crates/mun_runtime/src/test.rs @@ -550,9 +550,6 @@ fn marshal_struct() { test_shallow_copy(&mut foo, &foo2, &bool_data, "b"); let mut bar = qux.get::("0").unwrap(); - let bar2 = qux.get::("0").unwrap(); - test_shallow_copy(&mut bar, &bar2, &int_data, "0"); - test_shallow_copy(&mut bar, &bar2, &bool_data, "1"); // Specify invalid return type let bar_err = bar.get::("0"); @@ -734,3 +731,38 @@ fn test_primitive_types() { test_field(&mut foo, (12usize, 111usize), "l"); test_field(&mut foo, (13f64, 112f64), "m"); } + +#[test] +fn gc_trace() { + let mut driver = TestDriver::new( + r#" + pub struct Foo { + quz: float, + bar: Bar, + } + + pub struct Bar { + baz: int + } + + pub fn new_foo(): Foo { + Foo { + quz: 1.0, + bar: Bar { + baz: 3 + } + } + } + "#, + ); + + let value: StructRef = invoke_fn!(driver.runtime_mut(), "new_foo").unwrap(); + + assert_eq!(driver.runtime_mut().borrow().gc_collect(), false); + assert!(driver.runtime_mut().borrow().gc_stats().allocated_memory > 0); + + drop(value); + + assert_eq!(driver.runtime_mut().borrow().gc_collect(), true); + assert_eq!(driver.runtime_mut().borrow().gc_stats().allocated_memory, 0); +}