Skip to content

Commit 007897f

Browse files
mishushakovclaude
andauthored
feat: add 100% JSI/Hermes API coverage (#6)
* fix: update hermes submodule to static_h branch The default branch for hermes changed from main to static_h. This updates the submodule and fixes breaking API changes: the libhermes cmake target was renamed to hermesvm_a, ES6Promise/ES6Class config options were removed (always-on), withEnableBlockScoping was renamed to withES6BlockScoping, and static utility methods moved from HermesRuntime to the IHermesRootAPI interface. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add 100% JSI/Hermes API coverage with new bindings Adds ~30 new C binding functions across 4 phases: - Core JSI: BigInt::getInt64, queueMicrotask, deleteProperty (3 overloads), computed property access (3 overloads), prototype operations (3 overloads) - Hermes-specific: fatal handler, bytecode epilogue, code coverage profiler, per-runtime profiling, load segment, unique IDs (6 types), timezone cache - RuntimeConfig: 7 new JIT/bytecode config fields - Rust wrappers: Safe APIs for all new functions with lifetime safety - Examples: New prototypes example, enhanced advanced.rs with new features - Tests: 18 new tests covering all new APIs Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2c69c6f commit 007897f

18 files changed

Lines changed: 1140 additions & 55 deletions

File tree

.gitmodules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[submodule "libhermesabi-sys/hermes"]
22
path = libhermesabi-sys/hermes
33
url = https://github.com/facebook/hermes.git
4-
branch = main
4+
branch = static_h

README.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,19 @@ Rust wrapper for [Hermes](https://hermesengine.dev) JavaScript engine.
1010
## Quick start
1111

1212
```rust
13-
use rusty_hermes::Runtime;
13+
use rusty_hermes::{Runtime, hermes_op};
14+
15+
#[hermes_op]
16+
fn add(a: f64, b: f64) -> f64 { a + b }
1417

1518
let rt = Runtime::new().unwrap();
1619

1720
// Evaluate JavaScript
1821
let val = rt.eval("1 + 2").unwrap();
1922
assert_eq!(val.as_number(), Some(3.0));
2023

21-
// Register a host function
22-
rt.set_func("add", |a: f64, b: f64| -> f64 { a + b }).unwrap();
24+
// Register a host function and call it from JS
25+
add::register(&rt).unwrap();
2326
let result = rt.eval("add(10, 20)").unwrap();
2427
assert_eq!(result.as_number(), Some(30.0));
2528
```
@@ -79,15 +82,18 @@ rt.eval("add_points({x: 1, y: 2}, {x: 3, y: 4})").unwrap();
7982
- **Rich type conversions**`IntoJs`/`FromJs` for all numeric types, `String`, `bool`, `Option<T>`, `Vec<T>`, `HashMap<String, T>`, `BTreeMap<String, T>`, `HashSet<T>`, `BTreeSet<T>`
8083
- **Host functions** — register Rust closures as JS functions with automatic type conversion (up to 8 args)
8184
- **Host objects** — create JS objects backed by Rust callbacks for custom get/set/property enumeration
82-
- **Object manipulation** — get/set/has properties (string and PropNameId keys), property enumeration, instanceof, NativeState
85+
- **Object manipulation** — get/set/has/delete properties (string, PropNameId, and Value keys), property enumeration, instanceof, NativeState, prototype get/set/create
8386
- **ArrayBuffer** — create, read, and write raw byte buffers
8487
- **PreparedJavaScript** — pre-compile scripts for repeated evaluation
8588
- **Scope** — RAII handle scopes for GC pressure management
8689
- **WeakObject** — weak references to JS objects
87-
- **RuntimeConfig** — builder pattern for configuring eval, Promise, Proxy, Intl, microtask queue, etc.
90+
- **Microtask queue** — queue microtasks and drain them
91+
- **RuntimeConfig** — builder pattern for configuring eval, Proxy, Intl, microtask queue, JIT, async generators, and more
8892
- **Execution limits** — watch/unwatch time limits for runaway scripts
89-
- **Bytecode utilities** — check, validate, and prefetch Hermes bytecode
90-
- **Sampling profiler** — enable/disable profiling and dump traces
93+
- **Bytecode utilities** — check, validate, prefetch Hermes bytecode, and read bytecode epilogue
94+
- **Sampling profiler** — enable/disable profiling, per-runtime registration, and dump traces
95+
- **Code coverage profiler** — enable/disable code coverage tracking
96+
- **Unique IDs** — Hermes-specific unique identifiers for objects, strings, symbols, bigints, and prop names
9197
- **Lifetime safety** — all JS values carry a `'rt` lifetime tied to their `Runtime`, preventing use-after-free at compile time
9298
- **Derive macros**`#[derive(IntoJs, FromJs)]` for automatic Rust ↔ JS struct/enum conversion
9399
- **Ops system**`#[hermes_op]` attribute macro for declaring host functions with auto type conversion and error propagation
@@ -131,4 +137,5 @@ cargo run --example host_functions
131137
cargo run --example objects_and_arrays
132138
cargo run --example advanced
133139
cargo run --example derive
140+
cargo run --example prototypes
134141
```

examples/advanced.rs

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
//! Advanced features demo — RuntimeConfig, JSON, PreparedJS, ArrayBuffer,
2-
//! BigInt, Scope, and time limits.
2+
//! BigInt, Scope, time limits, microtask queue, unique IDs, and code coverage.
33
//!
44
//! Run with:
55
//! cargo run --example advanced
66
77
use rusty_hermes::{
8-
ArrayBuffer, BigInt, PropNameId, Runtime, RuntimeConfig, Scope,
8+
ArrayBuffer, BigInt, JsString, Object, PropNameId, Runtime, RuntimeConfig, Scope, Value,
99
};
1010

1111
fn main() {
@@ -32,6 +32,38 @@ fn main() {
3232
let version = obj.get_with_propname(&key).unwrap();
3333
println!("version (via PropNameId) = {}", version.as_number().unwrap());
3434

35+
// -- Delete property -----------------------------------------------------
36+
obj.set("temp", Value::from_number(99.0)).unwrap();
37+
assert!(obj.has("temp"));
38+
obj.delete("temp").unwrap();
39+
assert!(!obj.has("temp"));
40+
println!("Delete property: OK");
41+
42+
// -- Computed property access (Value key) ---------------------------------
43+
let arr_val = rt
44+
.create_value_from_json("[10, 20, 30]")
45+
.unwrap();
46+
let arr_obj = arr_val.into_object().unwrap();
47+
let idx = Value::from_number(1.0);
48+
let elem = arr_obj.get_with_value(&idx).unwrap();
49+
println!("arr[1] via Value key = {}", elem.as_number().unwrap());
50+
51+
// -- Unique IDs ----------------------------------------------------------
52+
let obj1 = Object::new(&rt);
53+
let obj2 = Object::new(&rt);
54+
println!(
55+
"Unique IDs: obj1={}, obj2={} (different={})",
56+
obj1.unique_id(),
57+
obj2.unique_id(),
58+
obj1.unique_id() != obj2.unique_id()
59+
);
60+
61+
let s1 = JsString::new(&rt, "hello");
62+
println!("String unique ID: {}", s1.unique_id());
63+
64+
let pn = PropNameId::from_utf8(&rt, "test");
65+
println!("PropNameId unique ID: {}", pn.unique_id());
66+
3567
// -- PreparedJavaScript (compile once, run many) -------------------------
3668
let prepared = rt.prepare_javascript("1 + 2 + 3", "compiled.js").unwrap();
3769
let r1 = rt.evaluate_prepared_javascript(&prepared).unwrap();
@@ -77,6 +109,11 @@ fn main() {
77109
let hex = big.to_js_string(16);
78110
println!("BigInt (hex): 0x{}", hex.to_rust_string().unwrap());
79111

112+
// BigInt i64 round-trip
113+
let neg = BigInt::from_i64(&rt, -42);
114+
assert_eq!(neg.truncate_to_i64(), -42);
115+
println!("BigInt i64 round-trip: -42 = {}", neg.truncate_to_i64());
116+
80117
// -- Scope ---------------------------------------------------------------
81118
{
82119
let _scope = Scope::new(&rt);
@@ -103,9 +140,35 @@ fn main() {
103140
println!("With time limit: {}", val.as_number().unwrap());
104141
rt.unwatch_time_limit();
105142

106-
// -- Drain microtasks ----------------------------------------------------
143+
// -- Queue microtask & drain ---------------------------------------------
144+
rt.eval("var microtaskResult = 0").unwrap();
145+
let func = rt
146+
.eval("(function() { microtaskResult = 123; })")
147+
.unwrap()
148+
.into_function()
149+
.unwrap();
150+
rt.queue_microtask(&func).unwrap();
107151
let drained = rt.drain_microtasks().unwrap();
108-
println!("Microtasks drained: {drained}");
152+
let result = rt.eval("microtaskResult").unwrap();
153+
println!(
154+
"Microtask: drained={drained}, result={}",
155+
result.as_number().unwrap()
156+
);
157+
158+
// -- Code coverage profiler ----------------------------------------------
159+
println!(
160+
"Code coverage profiler enabled: {}",
161+
Runtime::is_code_coverage_profiler_enabled()
162+
);
163+
164+
// -- Profiling registration ----------------------------------------------
165+
rt.register_for_profiling();
166+
rt.unregister_for_profiling();
167+
println!("Profiling register/unregister: OK");
168+
169+
// -- Reset timezone cache ------------------------------------------------
170+
rt.reset_timezone_cache();
171+
println!("Reset timezone cache: OK");
109172

110173
println!("\nAll advanced features working!");
111174
}

examples/prototypes.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//! Prototype operations demo — create objects with prototypes, get/set
2+
//! prototype chains, and demonstrate inheritance.
3+
//!
4+
//! Run with:
5+
//! cargo run --example prototypes
6+
7+
use rusty_hermes::{Object, Runtime, Value};
8+
9+
fn main() {
10+
let rt = Runtime::new().expect("failed to create Hermes runtime");
11+
12+
// Create a prototype object with a method
13+
let animal = Object::new(&rt);
14+
animal
15+
.set("type", Value::from(rusty_hermes::JsString::new(&rt, "animal")))
16+
.unwrap();
17+
animal
18+
.set("legs", Value::from_number(4.0))
19+
.unwrap();
20+
21+
println!("Prototype: type={}", {
22+
let t = animal.get("type").unwrap().into_string().unwrap();
23+
t.to_rust_string().unwrap()
24+
});
25+
26+
// Create a child object with the prototype
27+
let proto_val: Value = animal.into();
28+
let dog = Object::create_with_prototype(&rt, &proto_val).unwrap();
29+
dog.set("name", Value::from(rusty_hermes::JsString::new(&rt, "Rex")))
30+
.unwrap();
31+
32+
// Child inherits properties from prototype
33+
let inherited_type = dog.get("type").unwrap().into_string().unwrap();
34+
println!(
35+
"Dog name={}, inherited type={}",
36+
dog.get("name")
37+
.unwrap()
38+
.into_string()
39+
.unwrap()
40+
.to_rust_string()
41+
.unwrap(),
42+
inherited_type.to_rust_string().unwrap()
43+
);
44+
45+
// Verify prototype chain
46+
let retrieved = dog.get_prototype().unwrap();
47+
assert!(retrieved.is_object());
48+
println!("get_prototype() returned an object: OK");
49+
50+
// Change prototype dynamically
51+
let new_proto = Object::new(&rt);
52+
new_proto
53+
.set("sound", Value::from(rusty_hermes::JsString::new(&rt, "meow")))
54+
.unwrap();
55+
let new_proto_val: Value = new_proto.into();
56+
dog.set_prototype(&new_proto_val).unwrap();
57+
58+
let sound = dog.get("sound").unwrap().into_string().unwrap();
59+
println!("After set_prototype, sound = {}", sound.to_rust_string().unwrap());
60+
61+
// Old inherited property is gone
62+
let old_type = dog.get("type").unwrap();
63+
assert!(old_type.is_undefined());
64+
println!("Old inherited 'type' is now undefined: OK");
65+
66+
// Unique IDs
67+
let obj_a = Object::new(&rt);
68+
let obj_b = Object::new(&rt);
69+
println!(
70+
"Unique IDs: a={}, b={} (different={})",
71+
obj_a.unique_id(),
72+
obj_b.unique_id(),
73+
obj_a.unique_id() != obj_b.unique_id()
74+
);
75+
76+
println!("\nAll prototype operations working!");
77+
}

libhermesabi-sys/README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,21 @@ unsafe {
3737

3838
## API surface
3939

40-
- **Runtime** — create (default/custom config), delete, evaluate JS, evaluate with source map, drain microtasks, get global object, parse JSON, description, inspectable
41-
- **RuntimeConfig** — eval, Promise, Proxy, Class, Intl, microtask queue, generators, block scoping, HermesInternal, max registers
40+
- **Runtime** — create (default/custom config), delete, evaluate JS, evaluate with source map, drain microtasks, queue microtask, get global object, parse JSON, description, inspectable, register/unregister for profiling, load segment, reset timezone cache
41+
- **RuntimeConfig** — eval, Proxy, Intl, microtask queue, generators, block scoping, HermesInternal, max registers, JIT (enable/force/threshold/memory limit), async generators, bytecode warmup percent, randomize memory layout
4242
- **PreparedJavaScript** — prepare, evaluate, delete
4343
- **Scope** — push/pop handle scopes
44-
- **String** — create from UTF-8/ASCII, convert to UTF-8, equality, release
45-
- **PropNameID** — create from string/UTF-8/ASCII/symbol, convert to UTF-8, equality, release
46-
- **Object** — create, get/set/has property (string and PropNameID keys), property names, type checks, instanceof, external memory pressure, NativeState (has/get/set), HostObject (create/get/is), release
44+
- **String** — create from UTF-8/ASCII, convert to UTF-8, equality, unique ID, release
45+
- **PropNameID** — create from string/UTF-8/ASCII/symbol, convert to UTF-8, equality, unique ID, release
46+
- **Object** — create, get/set/has/delete property (string, PropNameID, and Value keys), property names, type checks, instanceof, external memory pressure, NativeState (has/get/set), HostObject (create/get/is), prototype (create/get/set), unique ID, release
4747
- **Array** — create, size, get/set by index, release
4848
- **ArrayBuffer** — create, size, data pointer
4949
- **Function** — call (with/without this), call as constructor, create from host function, is host function, release
50-
- **Value** — release, strict equality, to string, clone
51-
- **Symbol** — to string, equality, release
52-
- **BigInt** — create from i64/u64, type checks, truncate, to string, equality, release
50+
- **Value** — release, strict equality, to string, clone, unique ID
51+
- **Symbol** — to string, equality, unique ID, release
52+
- **BigInt** — create from i64/u64, type checks, truncate to u64, get i64, to string, equality, unique ID, release
5353
- **WeakObject** — create, lock, release
54-
- **Hermes-specific** — bytecode check/version/sanity/prefetch, time limits (watch/unwatch/trigger), sampling profiler (enable/disable/dump)
54+
- **Hermes-specific** — bytecode check/version/sanity/prefetch/epilogue, time limits (watch/unwatch/trigger), sampling profiler (enable/disable/dump), code coverage profiler (enable/disable/query), fatal handler, unique IDs for all pointer types
5555

5656
## Installation
5757

libhermesabi-sys/build.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ fn main() {
1313
println!("cargo:rerun-if-changed=src/binding.hpp");
1414
println!("cargo:rerun-if-changed={}/", hermes_src_dir);
1515

16-
// Build Hermes via cmake, targeting libhermes (JSI implementation).
16+
// Build Hermes via cmake, targeting hermesvm_a (static VM with compiler).
1717
// All libraries are built as static archives.
1818
let hermes_build = Config::new(hermes_src_dir)
19-
.build_target("libhermes")
19+
.build_target("hermesvm_a")
2020
.configure_arg("-G Ninja")
2121
.define("HERMES_ENABLE_EH_RTTI", "ON")
2222
.define("BUILD_SHARED_LIBS", "OFF")

libhermesabi-sys/hermes

Submodule hermes updated 5105 files

0 commit comments

Comments
 (0)