Skip to content

Commit a73e0ba

Browse files
committed
Reimplement Hashtable with Java-style Entry[] array and separate chaining
Replace Rust Arc<Mutex<HashMap>> backing with proper Java fields (table, count, threshold) and Hashtable$Entry inner class with linked-list chaining. Implements rehash with capacity old*2+1 and 0.75 load factor. Remove all Hashtable-specific GC hacks from garbage_collector.rs as generic field traversal now handles reachability. Add GC test for Hashtable verifying proper collection of entries.
1 parent 1a36784 commit a73e0ba

File tree

6 files changed

+201
-121
lines changed

6 files changed

+201
-121
lines changed

java_runtime/src/classes/java/util.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod empty_stack_exception;
1010
mod enumeration;
1111
mod gregorian_calendar;
1212
mod hashtable;
13+
mod hashtable_entry;
1314
mod properties;
1415
mod random;
1516
mod simple_time_zone;
@@ -22,7 +23,7 @@ mod vector;
2223

2324
pub use self::{
2425
abstract_collection::AbstractCollection, abstract_list::AbstractList, calendar::Calendar, date::Date, dictionary::Dictionary,
25-
empty_stack_exception::EmptyStackException, enumeration::Enumeration, gregorian_calendar::GregorianCalendar, hashtable::Hashtable,
26+
empty_stack_exception::EmptyStackException, enumeration::Enumeration, gregorian_calendar::GregorianCalendar, hashtable::Hashtable, hashtable_entry::HashtableEntry,
2627
properties::Properties, random::Random, simple_time_zone::SimpleTimeZone, stack::Stack, time_zone::TimeZone, timer::Timer, timer_task::TimerTask,
2728
timer_thread::TimerThread, vector::Vector,
2829
};
Lines changed: 141 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
1-
use alloc::{boxed::Box, sync::Arc, vec, vec::Vec};
2-
use core::mem;
3-
4-
use hashbrown::HashMap;
5-
use parking_lot::Mutex;
1+
use alloc::vec;
62

73
use java_class_proto::{JavaFieldProto, JavaMethodProto};
8-
use jvm::{ClassInstance, ClassInstanceRef, Jvm, Result};
4+
use jvm::{ClassInstanceRef, Jvm, Result};
95

106
use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object};
117

12-
// I'm too lazy to implement hashmap in java, so i'm leveraging rust hashmap here...
13-
// We can't use java object as hashmap key as we need `await` to call `equals()`
14-
type RustHashMap = Arc<Mutex<HashMap<i32, Vec<(Box<dyn ClassInstance>, Box<dyn ClassInstance>)>>>>;
8+
use super::HashtableEntry;
9+
10+
const DEFAULT_INITIAL_CAPACITY: i32 = 11;
11+
const DEFAULT_LOAD_FACTOR: f32 = 0.75;
1512

1613
// class java.util.Hashtable
1714
pub struct Hashtable;
@@ -34,131 +31,207 @@ impl Hashtable {
3431
JavaMethodProto::new("get", "(Ljava/lang/Object;)Ljava/lang/Object;", Self::get, Default::default()),
3532
JavaMethodProto::new("remove", "(Ljava/lang/Object;)Ljava/lang/Object;", Self::remove, Default::default()),
3633
],
37-
fields: vec![JavaFieldProto::new("raw", "[B", Default::default())],
34+
fields: vec![
35+
JavaFieldProto::new("table", "[Ljava/util/Hashtable$Entry;", Default::default()),
36+
JavaFieldProto::new("count", "I", Default::default()),
37+
JavaFieldProto::new("threshold", "I", Default::default()),
38+
],
3839
access_flags: Default::default(),
3940
}
4041
}
4142

4243
async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef<Self>) -> Result<()> {
43-
tracing::debug!("java.util.Hashtable::<init>({:?})", &this);
44+
tracing::debug!("java.util.Hashtable::<init>({this:?})");
4445

4546
let _: () = jvm.invoke_special(&this, "java/util/Dictionary", "<init>", "()V", ()).await?;
4647

47-
let rust_hash_map: RustHashMap = Arc::new(Mutex::new(HashMap::new()));
48-
jvm.put_rust_object_field(&mut this, "raw", rust_hash_map).await?;
48+
let table = jvm.instantiate_array("Ljava/util/Hashtable$Entry;", DEFAULT_INITIAL_CAPACITY as _).await?;
49+
jvm.put_field(&mut this, "table", "[Ljava/util/Hashtable$Entry;", table).await?;
50+
jvm.put_field(&mut this, "count", "I", 0).await?;
51+
jvm.put_field(&mut this, "threshold", "I", (DEFAULT_INITIAL_CAPACITY as f32 * DEFAULT_LOAD_FACTOR) as i32).await?;
4952

5053
Ok(())
5154
}
5255

53-
// TODO we need to add synchronized
5456
async fn contains_key(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef<Self>, key: ClassInstanceRef<Object>) -> Result<bool> {
55-
tracing::debug!("java.util.Hashtable::containsKey({:?}, {:?})", &this, &key);
57+
tracing::debug!("java.util.Hashtable::containsKey({this:?}, {key:?})");
5658

57-
let rust_hash_map = Self::get_rust_hashmap(jvm, &this).await?;
5859
let key_hash: i32 = jvm.invoke_virtual(&key, "hashCode", "()I", ()).await?;
59-
60-
let vec = rust_hash_map.lock().get(&key_hash).cloned();
61-
62-
if let Some(x) = vec {
63-
for (key, _) in &x {
64-
let equals = jvm.invoke_virtual(key, "equals", "(Ljava/lang/Object;)Z", ((*key).clone(),)).await?;
60+
let table = jvm.get_field(&this, "table", "[Ljava/util/Hashtable$Entry;").await?;
61+
let table_len = jvm.array_length(&table).await? as i32;
62+
let bucket_index = ((key_hash & 0x7FFFFFFF) % table_len) as usize;
63+
64+
let mut entry: ClassInstanceRef<HashtableEntry> = jvm.load_array(&table, bucket_index, 1).await?.into_iter().next().unwrap();
65+
while !entry.is_null() {
66+
let entry_hash: i32 = jvm.get_field(&entry, "hash", "I").await?;
67+
if entry_hash == key_hash {
68+
let entry_key: ClassInstanceRef<Object> = jvm.get_field(&entry, "key", "Ljava/lang/Object;").await?;
69+
let equals: bool = jvm.invoke_virtual(&entry_key, "equals", "(Ljava/lang/Object;)Z", (key.clone(),)).await?;
6570
if equals {
6671
return Ok(true);
6772
}
6873
}
74+
entry = jvm.get_field(&entry, "next", "Ljava/util/Hashtable$Entry;").await?;
6975
}
76+
7077
Ok(false)
7178
}
7279

73-
// TODO we need to add synchronized
7480
async fn get(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef<Self>, key: ClassInstanceRef<Object>) -> Result<ClassInstanceRef<Object>> {
75-
tracing::debug!("java.util.Hashtable::get({:?}, {:?})", &this, &key);
81+
tracing::debug!("java.util.Hashtable::get({this:?}, {key:?})");
7682

77-
let rust_hash_map = Self::get_rust_hashmap(jvm, &this).await?;
7883
let key_hash: i32 = jvm.invoke_virtual(&key, "hashCode", "()I", ()).await?;
79-
80-
let vec = rust_hash_map.lock().get(&key_hash).cloned();
81-
82-
if let Some(x) = vec {
83-
for (key, value) in &x {
84-
let equals = jvm.invoke_virtual(key, "equals", "(Ljava/lang/Object;)Z", ((*key).clone(),)).await?;
84+
let table = jvm.get_field(&this, "table", "[Ljava/util/Hashtable$Entry;").await?;
85+
let table_len = jvm.array_length(&table).await? as i32;
86+
let bucket_index = ((key_hash & 0x7FFFFFFF) % table_len) as usize;
87+
88+
let mut entry: ClassInstanceRef<HashtableEntry> = jvm.load_array(&table, bucket_index, 1).await?.into_iter().next().unwrap();
89+
while !entry.is_null() {
90+
let entry_hash: i32 = jvm.get_field(&entry, "hash", "I").await?;
91+
if entry_hash == key_hash {
92+
let entry_key: ClassInstanceRef<Object> = jvm.get_field(&entry, "key", "Ljava/lang/Object;").await?;
93+
let equals: bool = jvm.invoke_virtual(&entry_key, "equals", "(Ljava/lang/Object;)Z", (key.clone(),)).await?;
8594
if equals {
86-
return Ok(value.clone().into());
95+
return jvm.get_field(&entry, "value", "Ljava/lang/Object;").await;
8796
}
8897
}
98+
entry = jvm.get_field(&entry, "next", "Ljava/util/Hashtable$Entry;").await?;
8999
}
90100

91101
Ok(None.into())
92102
}
93103

94-
// TODO we need to add synchronized
95104
async fn remove(
96105
jvm: &Jvm,
97106
_: &mut RuntimeContext,
98-
this: ClassInstanceRef<Self>,
107+
mut this: ClassInstanceRef<Self>,
99108
key: ClassInstanceRef<Object>,
100109
) -> Result<ClassInstanceRef<Object>> {
101-
tracing::debug!("java.util.Hashtable::remove({:?}, {:?})", &this, &key);
110+
tracing::debug!("java.util.Hashtable::remove({this:?}, {key:?})");
102111

103-
let rust_hash_map = Self::get_rust_hashmap(jvm, &this).await?;
104112
let key_hash: i32 = jvm.invoke_virtual(&key, "hashCode", "()I", ()).await?;
105-
106-
let vec = rust_hash_map.lock().get(&key_hash).cloned();
107-
108-
if let Some(x) = vec {
109-
for (i, (bucket_key, _)) in x.iter().enumerate() {
110-
let equals = jvm.invoke_virtual(bucket_key, "equals", "(Ljava/lang/Object;)Z", (key.clone(),)).await?;
113+
let mut table = jvm.get_field(&this, "table", "[Ljava/util/Hashtable$Entry;").await?;
114+
let table_len = jvm.array_length(&table).await? as i32;
115+
let bucket_index = ((key_hash & 0x7FFFFFFF) % table_len) as usize;
116+
117+
let mut prev: ClassInstanceRef<HashtableEntry> = None.into();
118+
let mut entry: ClassInstanceRef<HashtableEntry> = jvm.load_array(&table, bucket_index, 1).await?.into_iter().next().unwrap();
119+
120+
while !entry.is_null() {
121+
let entry_hash: i32 = jvm.get_field(&entry, "hash", "I").await?;
122+
if entry_hash == key_hash {
123+
let entry_key: ClassInstanceRef<Object> = jvm.get_field(&entry, "key", "Ljava/lang/Object;").await?;
124+
let equals: bool = jvm.invoke_virtual(&entry_key, "equals", "(Ljava/lang/Object;)Z", (key.clone(),)).await?;
111125
if equals {
112-
let (_, old_value) = rust_hash_map.lock().get_mut(&key_hash).unwrap().remove(i);
126+
let next: ClassInstanceRef<HashtableEntry> = jvm.get_field(&entry, "next", "Ljava/util/Hashtable$Entry;").await?;
127+
if prev.is_null() {
128+
jvm.store_array(&mut table, bucket_index, core::iter::once(next)).await?;
129+
} else {
130+
jvm.put_field(&mut prev, "next", "Ljava/util/Hashtable$Entry;", next).await?;
131+
}
113132

114-
return Ok(old_value.into());
133+
let count: i32 = jvm.get_field(&this, "count", "I").await?;
134+
jvm.put_field(&mut this, "count", "I", count - 1).await?;
135+
136+
return jvm.get_field(&entry, "value", "Ljava/lang/Object;").await;
115137
}
116138
}
139+
prev = entry;
140+
entry = jvm.get_field(&prev, "next", "Ljava/util/Hashtable$Entry;").await?;
117141
}
118142

119143
Ok(None.into())
120144
}
121145

122-
// TODO we need to add synchronized
123146
async fn put(
124147
jvm: &Jvm,
125148
_: &mut RuntimeContext,
126-
this: ClassInstanceRef<Self>,
149+
mut this: ClassInstanceRef<Self>,
127150
key: ClassInstanceRef<Object>,
128151
value: ClassInstanceRef<Object>,
129152
) -> Result<ClassInstanceRef<Object>> {
130-
tracing::debug!("java.util.Hashtable::put({:?}, {:?}, {:?})", &this, &key, &value);
153+
tracing::debug!("java.util.Hashtable::put({this:?}, {key:?}, {value:?})");
131154

132-
let rust_hash_map = Self::get_rust_hashmap(jvm, &this).await?;
133155
let key_hash: i32 = jvm.invoke_virtual(&key, "hashCode", "()I", ()).await?;
134-
135-
let vec = {
136-
let mut rust_hash_map = rust_hash_map.lock();
137-
if !rust_hash_map.contains_key(&key_hash) {
138-
rust_hash_map.insert(key_hash, Vec::new());
156+
let mut table = jvm.get_field(&this, "table", "[Ljava/util/Hashtable$Entry;").await?;
157+
let table_len = jvm.array_length(&table).await? as i32;
158+
let bucket_index = ((key_hash & 0x7FFFFFFF) % table_len) as usize;
159+
160+
let mut entry: ClassInstanceRef<HashtableEntry> = jvm.load_array(&table, bucket_index, 1).await?.into_iter().next().unwrap();
161+
while !entry.is_null() {
162+
let entry_hash: i32 = jvm.get_field(&entry, "hash", "I").await?;
163+
if entry_hash == key_hash {
164+
let entry_key: ClassInstanceRef<Object> = jvm.get_field(&entry, "key", "Ljava/lang/Object;").await?;
165+
let equals: bool = jvm.invoke_virtual(&entry_key, "equals", "(Ljava/lang/Object;)Z", (key.clone(),)).await?;
166+
if equals {
167+
let old_value: ClassInstanceRef<Object> = jvm.get_field(&entry, "value", "Ljava/lang/Object;").await?;
168+
jvm.put_field(&mut entry, "value", "Ljava/lang/Object;", value).await?;
169+
return Ok(old_value);
170+
}
139171
}
172+
entry = jvm.get_field(&entry, "next", "Ljava/util/Hashtable$Entry;").await?;
173+
}
174+
175+
let count: i32 = jvm.get_field(&this, "count", "I").await?;
176+
let threshold: i32 = jvm.get_field(&this, "threshold", "I").await?;
177+
178+
if count >= threshold {
179+
Self::rehash(jvm, &mut this).await?;
180+
table = jvm.get_field(&this, "table", "[Ljava/util/Hashtable$Entry;").await?;
181+
let new_table_len = jvm.array_length(&table).await? as i32;
182+
let new_bucket_index = ((key_hash & 0x7FFFFFFF) % new_table_len) as usize;
183+
184+
let existing: ClassInstanceRef<HashtableEntry> = jvm.load_array(&table, new_bucket_index, 1).await?.into_iter().next().unwrap();
185+
let new_entry = jvm
186+
.new_class(
187+
"java/util/Hashtable$Entry",
188+
"(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/Hashtable$Entry;)V",
189+
(key_hash, key, value, existing),
190+
)
191+
.await?;
192+
jvm.store_array(&mut table, new_bucket_index, core::iter::once(new_entry)).await?;
193+
} else {
194+
let existing: ClassInstanceRef<HashtableEntry> = jvm.load_array(&table, bucket_index, 1).await?.into_iter().next().unwrap();
195+
let new_entry = jvm
196+
.new_class(
197+
"java/util/Hashtable$Entry",
198+
"(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/Hashtable$Entry;)V",
199+
(key_hash, key, value, existing),
200+
)
201+
.await?;
202+
jvm.store_array(&mut table, bucket_index, core::iter::once(new_entry)).await?;
203+
}
140204

141-
rust_hash_map.get(&key_hash).cloned().unwrap()
142-
};
205+
jvm.put_field(&mut this, "count", "I", count + 1).await?;
206+
207+
Ok(None.into())
208+
}
143209

144-
for (i, (bucket_key, _)) in vec.iter().enumerate() {
145-
let equals = jvm.invoke_virtual(bucket_key, "equals", "(Ljava/lang/Object;)Z", (key.clone(),)).await?;
146-
if equals {
147-
let mut rust_hash_map = rust_hash_map.lock();
148-
let vec = rust_hash_map.get_mut(&key_hash).unwrap();
210+
async fn rehash(jvm: &Jvm, this: &mut ClassInstanceRef<Self>) -> Result<()> {
211+
let old_table = jvm.get_field(this, "table", "[Ljava/util/Hashtable$Entry;").await?;
212+
let old_capacity = jvm.array_length(&old_table).await?;
213+
let new_capacity = old_capacity * 2 + 1;
149214

150-
let (_, old_value) = mem::replace(&mut vec[i], (key.into(), value.into()));
215+
let mut new_table = jvm.instantiate_array("Ljava/util/Hashtable$Entry;", new_capacity).await?;
151216

152-
return Ok(old_value.into());
217+
for i in 0..old_capacity {
218+
let mut entry: ClassInstanceRef<HashtableEntry> = jvm.load_array(&old_table, i, 1).await?.into_iter().next().unwrap();
219+
while !entry.is_null() {
220+
let next: ClassInstanceRef<HashtableEntry> = jvm.get_field(&entry, "next", "Ljava/util/Hashtable$Entry;").await?;
221+
let entry_hash: i32 = jvm.get_field(&entry, "hash", "I").await?;
222+
let new_index = ((entry_hash & 0x7FFFFFFF) % new_capacity as i32) as usize;
223+
224+
let existing: ClassInstanceRef<HashtableEntry> = jvm.load_array(&new_table, new_index, 1).await?.into_iter().next().unwrap();
225+
jvm.put_field(&mut entry, "next", "Ljava/util/Hashtable$Entry;", existing).await?;
226+
jvm.store_array(&mut new_table, new_index, core::iter::once(entry)).await?;
227+
228+
entry = next;
153229
}
154230
}
155231

156-
rust_hash_map.lock().get_mut(&key_hash).unwrap().push((key.into(), value.into()));
157-
158-
Ok(None.into())
159-
}
232+
jvm.put_field(this, "table", "[Ljava/util/Hashtable$Entry;", new_table).await?;
233+
jvm.put_field(this, "threshold", "I", (new_capacity as f32 * DEFAULT_LOAD_FACTOR) as i32).await?;
160234

161-
async fn get_rust_hashmap(jvm: &Jvm, this: &ClassInstanceRef<Self>) -> Result<RustHashMap> {
162-
jvm.get_rust_object_field(this, "raw").await
235+
Ok(())
163236
}
164237
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use alloc::vec;
2+
3+
use java_class_proto::{JavaFieldProto, JavaMethodProto};
4+
use jvm::{ClassInstanceRef, Jvm, Result};
5+
6+
use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object};
7+
8+
// class java.util.Hashtable$Entry
9+
pub struct HashtableEntry;
10+
11+
impl HashtableEntry {
12+
pub fn as_proto() -> RuntimeClassProto {
13+
RuntimeClassProto {
14+
name: "java/util/Hashtable$Entry",
15+
parent_class: Some("java/lang/Object"),
16+
interfaces: vec![],
17+
methods: vec![JavaMethodProto::new("<init>", "(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/Hashtable$Entry;)V", Self::init, Default::default())],
18+
fields: vec![
19+
JavaFieldProto::new("hash", "I", Default::default()),
20+
JavaFieldProto::new("key", "Ljava/lang/Object;", Default::default()),
21+
JavaFieldProto::new("value", "Ljava/lang/Object;", Default::default()),
22+
JavaFieldProto::new("next", "Ljava/util/Hashtable$Entry;", Default::default()),
23+
],
24+
access_flags: Default::default(),
25+
}
26+
}
27+
28+
async fn init(
29+
jvm: &Jvm,
30+
_: &mut RuntimeContext,
31+
mut this: ClassInstanceRef<Self>,
32+
hash: i32,
33+
key: ClassInstanceRef<Object>,
34+
value: ClassInstanceRef<Object>,
35+
next: ClassInstanceRef<HashtableEntry>,
36+
) -> Result<()> {
37+
tracing::debug!("java.util.Hashtable$Entry::<init>({this:?}, {hash:?}, {key:?}, {value:?}, {next:?})");
38+
39+
let _: () = jvm.invoke_special(&this, "java/lang/Object", "<init>", "()V", ()).await?;
40+
41+
jvm.put_field(&mut this, "hash", "I", hash).await?;
42+
jvm.put_field(&mut this, "key", "Ljava/lang/Object;", key).await?;
43+
jvm.put_field(&mut this, "value", "Ljava/lang/Object;", value).await?;
44+
jvm.put_field(&mut this, "next", "Ljava/util/Hashtable$Entry;", next).await?;
45+
46+
Ok(())
47+
}
48+
}

java_runtime/src/loader.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ pub fn get_runtime_class_proto(name: &str) -> Option<RuntimeClassProto> {
8080
crate::classes::java::util::Enumeration::as_proto(),
8181
crate::classes::java::util::GregorianCalendar::as_proto(),
8282
crate::classes::java::util::Hashtable::as_proto(),
83+
crate::classes::java::util::HashtableEntry::as_proto(),
8384
crate::classes::java::util::Properties::as_proto(),
8485
crate::classes::java::util::Random::as_proto(),
8586
crate::classes::java::util::SimpleTimeZone::as_proto(),

0 commit comments

Comments
 (0)