* [PATCH 00/15] Introduce rust: In xdiff
@ 2025-08-29 19:42 Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 01/15] doc: add a policy for using Rust brian m. carlson via GitGitGadget
` (14 more replies)
0 siblings, 15 replies; 20+ messages in thread
From: Ezekiel Newren via GitGitGadget @ 2025-08-29 19:42 UTC (permalink / raw)
To: git; +Cc: Ezekiel Newren
This is a continuation of
https://lore.kernel.org/git/pull.1980.v3.git.git.1755921356.gitgitgadget@gmail.com/,
but I am removing the RFC label.
Changes since the last RFC patch series (range-diff added below):
* Addressed feedback:
* Use self instead of this
* Update ivec to use Git code style
* Remove environment variable CI_IS_DOCKER from github workflows
* Remove PHONY target interop-objs
* Removed left over asserts in Rust and fix near by code
* Other changes:
* Avoid name mangling of ivec if c++ compiler used
* Fixed a typo in patch 11 commit message
High level overview:
* patch 1: add a policy for using Rust (brian's patch, with a small tweak)
* patch 2: introduce Rust to the codebase
* patches 3-5: adapt CI (github workflows) to build Git with Rust
* patch 6: introduce the ivec type
* patches 7-14: xdiff code cleanup in preparation for translating to Rust
* patch 15: translate a C function into Rust and call it from C
Build results for these changes:
https://github.com/git/git/actions/runs/17330190596
Range-Diff against last RFC version:
1: 6d065f550f = 1: 6d065f550f doc: add a policy for using Rust
2: 0393995125 = 2: cc9cf87775 xdiff: introduce rust
3: a98d9e4d21 ! 3: 6c47401ba0 github workflows: install rust
@@ .github/workflows/main.yml: jobs:
image: fedora:latest
# A RHEL 8 compatible distro. Supported until 2029-05-31.
@@ .github/workflows/main.yml: jobs:
- jobname: ${{matrix.vector.jobname}}
CC: ${{matrix.vector.cc}}
CI_JOB_IMAGE: ${{matrix.vector.image}}
-+ CI_IS_DOCKER: "true"
CUSTOM_PATH: /custom
+ CARGO_HOME: /home/builder/.cargo
runs-on: ubuntu-latest
4: 0d2b39c3e0 = 4: 8e350700bc win+Meson: do allow linking with the Rust-built xdiff
5: e65488ab99 = 5: a00cbf7bcb github workflows: upload Cargo.lock
6: db5d22b188 ! 6: 922d506ed6 ivec: create a vector type that is interoperable between C and Rust
@@ Makefile: reconfigure config.mak.autogen: config.status
endif
+INTEROP_OBJS += interop/ivec.o
-+.PHONY: interop-objs
-+interop-objs: $(INTEROP_OBJS)
+
XDIFF_OBJS += xdiff/xdiffi.o
XDIFF_OBJS += xdiff/xemit.o
@@ interop/ivec.c (new)
@@
+#include "ivec.h"
+
-+static void ivec_set_capacity(void* self, usize new_capacity) {
-+ struct rawivec *this = self;
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++static void ivec_set_capacity(void *self_, usize new_capacity)
++{
++ struct rawivec *self = self_;
++
+ if (new_capacity == 0)
-+ FREE_AND_NULL(this->ptr);
++ FREE_AND_NULL(self->ptr);
+ else
-+ this->ptr = xrealloc(this->ptr, new_capacity * this->element_size);
-+ this->capacity = new_capacity;
++ self->ptr = xrealloc(self->ptr, new_capacity * self->element_size);
++ self->capacity = new_capacity;
+}
+
-+void ivec_init(void* self, usize element_size) {
-+ struct rawivec *this = self;
-+ this->ptr = NULL;
-+ this->length = 0;
-+ this->capacity = 0;
-+ this->element_size = element_size;
++void ivec_init(void *self_, usize element_size)
++{
++ struct rawivec *self = self_;
++
++ self->ptr = NULL;
++ self->length = 0;
++ self->capacity = 0;
++ self->element_size = element_size;
+}
+
+/*
@@ interop/ivec.c (new)
+ * to the specified capacity, and then calloc self.capacity number of
+ * elements.
+ */
-+void ivec_zero(void* self, usize capacity) {
-+ struct rawivec *this = self;
-+ if (this->ptr)
-+ FREE_AND_NULL(this->ptr);
-+ this->capacity = this->length = capacity;
-+ this->ptr = xcalloc(this->capacity, this->element_size);
++void ivec_zero(void *self_, usize capacity)
++{
++ struct rawivec *self = self_;
++
++ if (self->ptr)
++ FREE_AND_NULL(self->ptr);
++ self->capacity = self->length = capacity;
++ self->ptr = xcalloc(self->capacity, self->element_size);
+}
+
-+void ivec_clear(void* self) {
-+ struct rawivec *this = self;
-+ this->length = 0;
++void ivec_clear(void *self_)
++{
++ struct rawivec *self = self_;
++
++ self->length = 0;
+}
+
-+void ivec_reserve_exact(void* self, usize additional) {
-+ struct rawivec *this = self;
-+ usize new_capacity = this->capacity + additional;
++void ivec_reserve_exact(void *self_, usize additional)
++{
++ struct rawivec *self = self_;
++ usize new_capacity = self->capacity + additional;
++
+ ivec_set_capacity(self, new_capacity);
+}
+
-+void ivec_reserve(void* self, usize additional) {
-+ struct rawivec *this = self;
++void ivec_reserve(void *self_, usize additional)
++{
++ struct rawivec *self = self_;
+ usize growby = 128;
-+ if (this->capacity > growby) {
-+ growby = this->capacity;
++
++ if (self->capacity > growby) {
++ growby = self->capacity;
+ }
+ if (additional > growby) {
+ growby = additional;
@@ interop/ivec.c (new)
+ ivec_reserve_exact(self, growby);
+}
+
-+void ivec_shrink_to_fit(void* self) {
-+ struct rawivec *this = self;
-+ ivec_set_capacity(self, this->length);
++void ivec_shrink_to_fit(void *self_)
++{
++ struct rawivec *self = self_;
++
++ ivec_set_capacity(self_, self->length);
+}
+
-+void ivec_resize(void* self, usize new_length, void* default_value) {
-+ struct rawivec *this = self;
-+ isize additional = (isize) (new_length - this->capacity);
++void ivec_resize(void *self_, usize new_length, void *default_value)
++{
++ struct rawivec *self = self_;
++ isize additional = (isize) (new_length - self->capacity);
++
+ if (additional > 0) {
-+ ivec_reserve(self, additional);
++ ivec_reserve(self_, additional);
+ }
+
-+ for (usize i = this->length; i < new_length; i++) {
-+ void* dst = (u8*) this->ptr + (this->length + i) * this->element_size;
-+ memcpy(dst, default_value, this->element_size);
++ for (usize i = self->length; i < new_length; i++) {
++ void *dst = (u8 *)self->ptr + (self->length + i) * self->element_size;
++ memcpy(dst, default_value, self->element_size);
+ }
-+ this->length = new_length;
++ self->length = new_length;
+}
+
-+void ivec_push(void* self, void* value) {
-+ struct rawivec *this = self;
-+ u8* dst;
++void ivec_push(void *self_, void *value)
++{
++ struct rawivec *self = self_;
++ u8 *dst;
+
-+ if (this->length == this->capacity) {
-+ ivec_reserve(self, 1);
++ if (self->length == self->capacity) {
++ ivec_reserve(self_, 1);
+ }
-+ dst = (u8*) this->ptr + this->length * this->element_size;
-+ memcpy(dst, value, this->element_size);
-+ this->length++;
++ dst = (u8 *)self->ptr + self->length * self->element_size;
++ memcpy(dst, value, self->element_size);
++ self->length++;
+}
+
-+void ivec_extend_from_slice(void* self, void const* ptr, usize size) {
-+ struct rawivec *this = self;
-+ u8* dst;
++void ivec_extend_from_slice(void *self_, void const *ptr, usize size)
++{
++ struct rawivec *self = self_;
++ u8 *dst;
+
+ if (size == 0)
+ return;
+
-+ if (this->length + size > this->capacity) {
-+ ivec_reserve(self, this->capacity - this->length + size);
++ if (self->length + size > self->capacity) {
++ ivec_reserve(self_, self->capacity - self->length + size);
+ }
-+ dst = (u8*) this->ptr + this->length * this->element_size;
-+ memcpy(dst, ptr, size * this->element_size);
-+ this->length += size;
++ dst = (u8 *)self->ptr + self->length * self->element_size;
++ memcpy(dst, ptr, size * self->element_size);
++ self->length += size;
+}
+
-+bool ivec_equal(void* self, void* other) {
-+ struct rawivec *lhs = self;
++bool ivec_equal(void *self_, void *other)
++{
++ struct rawivec *lhs = self_;
+ struct rawivec *rhs = other;
+
+ if (lhs->element_size != rhs->element_size) {
@@ interop/ivec.c (new)
+ return false;
+ }
+
-+
+ for (usize i = 0; i < lhs->length; i++) {
-+ void* left = (u8 *) lhs->ptr + i * lhs->element_size;
-+ void* right = (u8 *) rhs->ptr + i * rhs->element_size;
++ void *left = (u8 *)lhs->ptr + i * lhs->element_size;
++ void *right = (u8 *)rhs->ptr + i * rhs->element_size;
+ if (memcmp(left, right, lhs->element_size) != 0) {
+ return false;
+ }
@@ interop/ivec.c (new)
+}
+
+
-+void ivec_free(void* self) {
-+ struct rawivec *this = self;
-+ FREE_AND_NULL(this->ptr);
-+ this->length = 0;
-+ this->capacity = 0;
++void ivec_free(void *self_)
++{
++ struct rawivec *self = self_;
++
++ FREE_AND_NULL(self->ptr);
++ self->length = 0;
++ self->capacity = 0;
+ /* don't modify self->element_size */
+}
+
-+void ivec_move(void* source, void* destination) {
-+ struct rawivec *this = source;
-+ struct rawivec *that = destination;
++void ivec_move(void *source, void *destination)
++{
++ struct rawivec *src = source;
++ struct rawivec *dst = destination;
+
-+ if (this->element_size != that->element_size)
++ if (src->element_size != dst->element_size)
+ BUG("mismatched element_size");
+
+ ivec_free(destination);
-+ that->ptr = this->ptr;
-+ that->length = this->length;
-+ that->capacity = this->capacity;
++ dst->ptr = src->ptr;
++ dst->length = src->length;
++ dst->capacity = src->capacity;
++
++ src->ptr = NULL;
++ src->length = 0;
++ src->capacity = 0;
++}
+
-+ this->ptr = NULL;
-+ this->length = 0;
-+ this->capacity = 0;
++#ifdef __cplusplus
+}
++#endif
## interop/ivec.h (new) ##
@@
+#ifndef IVEC_H
+#define IVEC_H
+
-+#include "../git-compat-util.h"
++#include <git-compat-util.h>
++
++#ifdef __cplusplus
++extern "C" {
++#endif
+
+struct rawivec {
-+ void* ptr;
++ void *ptr;
+ usize length;
+ usize capacity;
+ usize element_size;
@@ interop/ivec.h (new)
+
+#define DEFINE_IVEC_TYPE(type, suffix) \
+struct ivec_##suffix { \
-+ type* ptr; \
++ type *ptr; \
+ size_t length; \
+ size_t capacity; \
+ size_t element_size; \
@@ interop/ivec.h (new)
+DEFINE_IVEC_TYPE(usize, usize);
+DEFINE_IVEC_TYPE(isize, isize);
+
-+void ivec_init(void* self, usize element_size);
-+void ivec_zero(void* self, usize capacity);
-+void ivec_clear(void* self);
-+void ivec_reserve_exact(void* self, usize additional);
-+void ivec_reserve(void* self, usize additional);
-+void ivec_shrink_to_fit(void* self);
-+void ivec_resize(void* self, usize new_length, void* default_value);
-+void ivec_push(void* self, void* value);
-+void ivec_extend_from_slice(void* self, void const* ptr, usize size);
-+bool ivec_equal(void* self, void* other);
-+void ivec_free(void* self);
-+void ivec_move(void* source, void* destination);
++void ivec_init(void *self_, usize element_size);
++void ivec_zero(void *self_, usize capacity);
++void ivec_clear(void *self_);
++void ivec_reserve_exact(void *self_, usize additional);
++void ivec_reserve(void *self_, usize additional);
++void ivec_shrink_to_fit(void *self_);
++void ivec_resize(void *self_, usize new_length, void *default_value);
++void ivec_push(void *self_, void *value);
++void ivec_extend_from_slice(void *self_, void const *ptr, usize size);
++bool ivec_equal(void *self_, void *other);
++void ivec_free(void *self_);
++void ivec_move(void *source, void *destination);
++
++#ifdef __cplusplus
++}
++#endif
+
+#endif //IVEC_H
@@ rust/interop/src/ivec.rs (new)
+
+impl<T> Drop for IVec<T> {
+ fn drop(&mut self) {
-+ unsafe {
-+ self._free();
-+ }
++ self._set_capacity(0);
+ }
+}
+
@@ rust/interop/src/ivec.rs (new)
+ }
+ }
+
-+ fn _zero(&mut self) {
-+ self.ptr = std::ptr::null_mut();
-+ self.length = 0;
-+ self.capacity = 0;
-+ // DO NOT MODIFY element_size!!!
-+ }
-+
-+ unsafe fn _free(&mut self) {
-+ free(self.ptr as *mut std::ffi::c_void);
-+ self._zero();
-+ }
-+
+ fn _set_capacity(&mut self, new_capacity: usize) {
+ unsafe {
+ if new_capacity == self.capacity {
+ return;
+ }
++ if new_capacity < self.length {
++ self.truncate(new_capacity);
++ }
+ if new_capacity == 0 {
-+ self._free();
++ free(self.ptr as *mut c_void);
++ self.ptr = std::ptr::null_mut();
++ self.length = 0;
++ self.capacity = 0;
++ // DO NOT MODIFY element_size!!!
+ } else {
-+ let t = realloc(
-+ self.ptr as *mut std::ffi::c_void,
-+ new_capacity * size_of::<T>(),
-+ );
++ let t = realloc(self.ptr as *mut c_void, new_capacity * size_of::<T>());
+ if t.is_null() {
+ panic!("out of memory");
+ }
@@ rust/interop/src/ivec.rs (new)
+ /* capacity does not need to be changed */
+ }
+
++ /* IVec grows */
+ if new_length > self.length {
+ let range = self.length..new_length;
+ self._buffer_mut()[range].fill(default_value);
++ self.length = new_length;
+ }
+
-+ self.length = new_length;
++ /* IVec shrinks */
++ if new_length < self.length {
++ self.truncate(new_length);
++ }
+ }
+
+ fn _buffer_mut(&mut self) -> &mut [T] {
@@ rust/interop/src/ivec.rs (new)
+ self._resize(new_length, default_value, true);
+ }
+
++ pub fn truncate(&mut self, new_length: usize) {
++ if new_length >= self.length {
++ return;
++ }
++
++ if std::mem::needs_drop::<T>() {
++ let range = new_length..self.length;
++ for v in &mut self.as_mut_slice()[range] {
++ unsafe {
++ std::ptr::drop_in_place(v);
++ }
++ }
++ }
++
++ self.length = new_length;
++ }
++
+ pub fn insert(&mut self, index: usize, value: T) {
+ if self.length == self.capacity {
+ self.reserve(1);
@@ rust/interop/src/ivec.rs (new)
+#[cfg(test)]
+mod tests {
+ use crate::ivec::IVec;
++ use std::cell::RefCell;
++ use std::mem::size_of;
++ use std::ops::{Deref, DerefMut};
+ use std::panic;
++ use std::rc::Rc;
++
++ struct DropTest {
++ value: Rc<RefCell<u64>>,
++ }
++
++ impl DropTest {
++ fn new(value: Rc<RefCell<u64>>) -> Self {
++ Self { value }
++ }
++ }
++
++ impl Drop for DropTest {
++ fn drop(&mut self) {
++ *self.value.borrow_mut() -= 1;
++ }
++ }
++
++ #[test]
++ fn test_drop_elements() {
++ let counter = Rc::new(RefCell::new(0u64));
++ let size = 5;
++
++ /* drop whole IVec */
++ let mut vec = IVec::new();
++ for _ in 0..size {
++ let tmp = DropTest::new(counter.clone());
++ *tmp.value.borrow_mut() += 1;
++ vec.push(tmp);
++ }
++ let cur = *counter.borrow();
++ assert_eq!(size, cur);
++ drop(vec);
++
++ let cur = *counter.borrow();
++ assert_eq!(0u64, cur);
++
++ /* drop some elements */
++ let mut vec = IVec::new();
++ for i in 0..size {
++ let tmp = DropTest::new(counter.clone());
++ *tmp.value.borrow_mut() += 1;
++ vec.push(tmp);
++ }
++ let cur = *counter.borrow().deref();
++ assert_eq!(size, cur);
++
++ let expected = 2u64;
++ vec.truncate(expected as usize);
++
++ let cur = *counter.borrow();
++ assert_eq!(expected, cur);
++ drop(vec);
++ }
+
+ #[test]
+ fn test_panic_on_out_of_bounds() {
@@ rust/interop/src/ivec.rs (new)
+ let expected = vec.capacity + 10;
+ let default_value = 19;
+ vec.resize(expected, default_value);
-+ // assert_eq!(vec.capacity, vec.slice.len());
+ assert_eq!(expected, vec.length);
+ assert!(vec.capacity >= expected);
+ for i in 0..vec.length {
@@ rust/interop/src/ivec.rs (new)
+ }
+
+ vec.reserve(10);
-+ // assert_eq!(vec.capacity, vec.slice.len());
+ assert!(vec.capacity > vec.length);
+ let length_before = vec.length;
+ vec.shrink_to_fit();
+ assert_eq!(length_before, vec.length);
+ assert_eq!(vec.length, vec.capacity);
-+ // assert_eq!(vec.capacity, vec.slice.len());
+ }
+
+ #[test]
@@ rust/interop/src/ivec.rs (new)
+ assert_eq!(16, vec.element_size);
+ assert_eq!(size_of::<usize>() * 4, size_of::<IVec<u128>>());
+ }
-+
-+ #[test]
-+ fn test_manual_free() {
-+ type TestType = i16;
-+ let mut vec = IVec::<TestType>::new();
-+
-+ unsafe { vec._free() };
-+ assert!(vec.ptr.is_null());
-+ assert_eq!(0, vec.length);
-+ assert_eq!(0, vec.capacity);
-+ assert_eq!(size_of::<TestType>(), vec.element_size);
-+ }
+}
## rust/interop/src/lib.rs ##
@@ rust/interop/src/lib.rs
+ pub fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void;
+ pub fn free(ptr: *mut c_void);
+}
-
- ## rust/xdiff/src/lib.rs ##
-@@
-+
7: d4bed95463 = 7: 3a6f822e2a xdiff/xprepare: remove superfluous forward declarations
8: 7c68ce5349 = 8: c7cd71dae0 xdiff: delete unnecessary fields from xrecord_t and xdfile_t
9: e516ccc8c0 = 9: 1096c8f0a4 xdiff: make fields of xrecord_t Rust friendly
10: 21bfb9f088 = 10: 0cd2168512 xdiff: use one definition for freeing xdfile_t
11: 6ce0e252b3 ! 11: 7ea2dccd71 xdiff: replace chastore with an ivec in xdfile_t
@@ Commit message
xdiff: replace chastore with an ivec in xdfile_t
xdfile_t currently uses a chastore which functions as a memory pool and
- a vector which maps to the alocations created by the chastore. It seems
+ a vector which maps to the allocations created by the chastore. It seems
like xrecord_t used to be a linked list until the recs and nrec fields
were added. I think that xrecord_t.next was meant to be removed, but
was overlooked. This dual data structure setup make the code somewhat
12: 0cfc6cf26b = 12: d8f561e173 xdiff: delete nrec field from xdfile_t
13: cf0387d851 = 13: 15c2854a32 xdiff: delete recs field from xdfile_t
14: ea699135f9 = 14: 3dacb2c09d xdiff: make xdfile_t more rust friendly
15: b18544b74f ! 15: 9fdd23acf6 xdiff: implement xdl_trim_ends() in Rust
@@ rust/xdiff/src/lib.rs
@@
+pub mod xprepare;
+pub mod xtypes;
-
++
+use crate::xprepare::trim_ends;
+use crate::xtypes::xdfile;
+
Ezekiel Newren (13):
xdiff: introduce rust
github workflows: install rust
github workflows: upload Cargo.lock
ivec: create a vector type that is interoperable between C and Rust
xdiff/xprepare: remove superfluous forward declarations
xdiff: delete unnecessary fields from xrecord_t and xdfile_t
xdiff: make fields of xrecord_t Rust friendly
xdiff: use one definition for freeing xdfile_t
xdiff: replace chastore with an ivec in xdfile_t
xdiff: delete nrec field from xdfile_t
xdiff: delete recs field from xdfile_t
xdiff: make xdfile_t more rust friendly
xdiff: implement xdl_trim_ends() in Rust
Johannes Schindelin (1):
win+Meson: do allow linking with the Rust-built xdiff
brian m. carlson (1):
doc: add a policy for using Rust
.github/workflows/main.yml | 88 ++-
.gitignore | 3 +
Documentation/Makefile | 1 +
Documentation/technical/platform-support.adoc | 2 +
Documentation/technical/rust-support.adoc | 142 +++++
Makefile | 67 ++-
build_rust.sh | 57 ++
ci/install-dependencies.sh | 14 +-
ci/install-rust-toolchain.sh | 30 +
ci/install-rustup.sh | 25 +
ci/lib.sh | 1 +
ci/make-test-artifacts.sh | 9 +
ci/run-build-and-tests.sh | 13 +
config.mak.uname | 4 +
git-compat-util.h | 17 +
interop/ivec.c | 180 ++++++
interop/ivec.h | 60 ++
meson.build | 54 +-
rust/Cargo.toml | 6 +
rust/interop/Cargo.toml | 14 +
rust/interop/src/ivec.rs | 516 ++++++++++++++++++
rust/interop/src/lib.rs | 10 +
rust/xdiff/Cargo.toml | 15 +
rust/xdiff/src/lib.rs | 15 +
rust/xdiff/src/xprepare.rs | 27 +
rust/xdiff/src/xtypes.rs | 19 +
xdiff/xdiffi.c | 60 +-
xdiff/xdiffi.h | 8 +-
xdiff/xemit.c | 24 +-
xdiff/xhistogram.c | 2 +-
xdiff/xmerge.c | 72 +--
xdiff/xpatience.c | 16 +-
xdiff/xprepare.c | 271 ++++-----
xdiff/xtypes.h | 27 +-
xdiff/xutils.c | 12 +-
35 files changed, 1562 insertions(+), 319 deletions(-)
create mode 100644 Documentation/technical/rust-support.adoc
create mode 100755 build_rust.sh
create mode 100755 ci/install-rust-toolchain.sh
create mode 100755 ci/install-rustup.sh
create mode 100644 interop/ivec.c
create mode 100644 interop/ivec.h
create mode 100644 rust/Cargo.toml
create mode 100644 rust/interop/Cargo.toml
create mode 100644 rust/interop/src/ivec.rs
create mode 100644 rust/interop/src/lib.rs
create mode 100644 rust/xdiff/Cargo.toml
create mode 100644 rust/xdiff/src/lib.rs
create mode 100644 rust/xdiff/src/xprepare.rs
create mode 100644 rust/xdiff/src/xtypes.rs
base-commit: 16bd9f20a403117f2e0d9bcda6c6e621d3763e77
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2043%2Fezekielnewren%2Fintroduce_rust-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2043/ezekielnewren/introduce_rust-v1
Pull-Request: https://github.com/git/git/pull/2043
--
gitgitgadget
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 01/15] doc: add a policy for using Rust
2025-08-29 19:42 [PATCH 00/15] Introduce rust: In xdiff Ezekiel Newren via GitGitGadget
@ 2025-08-29 19:42 ` brian m. carlson via GitGitGadget
2025-08-29 20:00 ` brian m. carlson
2025-08-29 19:42 ` [PATCH 02/15] xdiff: introduce rust Ezekiel Newren via GitGitGadget
` (13 subsequent siblings)
14 siblings, 1 reply; 20+ messages in thread
From: brian m. carlson via GitGitGadget @ 2025-08-29 19:42 UTC (permalink / raw)
To: git; +Cc: Ezekiel Newren, brian m. carlson
From: "brian m. carlson" <sandals@crustytoothpaste.net>
Git has historically been written primarily in C, with some shell and
Perl. However, C is not memory safe, which makes it more likely that
security vulnerabilities or other bugs will be introduced, and it is
also more verbose and less ergonomic than other, more modern languages.
One of the most common modern compiled languages which is easily
interoperable with C is Rust. It is popular (the most admired language
on the 2024 Stack Overflow Developer Survey), efficient, portable, and
robust.
Introduce a document laying out the incremental introduction of Rust to
Git and provide a detailed rationale for doing so, including the points
above. Propose a design for this approach that addresses the needs of
downstreams and distributors, as well as contributors.
Since we don't want to carry both a C and Rust version of code and want
to be able to add new features only in Rust, mention that Rust is a
required part of our platform support policy.
It should be noted that a recent discussion at the Berlin Git Merge
Contributor Summit found widespread support for the addition of Rust to
Git. While of course not all contributors were represented, the
proposal appeared to have the support of a majority of active
contributors.
Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
[en: Added some comments about types, and changed the recommondations
about cbindgen, bindgen, rustix, libc.]
Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
---
Documentation/Makefile | 1 +
Documentation/technical/platform-support.adoc | 2 +
Documentation/technical/rust-support.adoc | 142 ++++++++++++++++++
3 files changed, 145 insertions(+)
create mode 100644 Documentation/technical/rust-support.adoc
diff --git a/Documentation/Makefile b/Documentation/Makefile
index b109d25e9c..066b761c01 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -127,6 +127,7 @@ TECH_DOCS += technical/parallel-checkout
TECH_DOCS += technical/partial-clone
TECH_DOCS += technical/platform-support
TECH_DOCS += technical/racy-git
+TECH_DOCS += technical/rust-support
TECH_DOCS += technical/reftable
TECH_DOCS += technical/scalar
TECH_DOCS += technical/send-pack-pipeline
diff --git a/Documentation/technical/platform-support.adoc b/Documentation/technical/platform-support.adoc
index 0a2fb28d62..dc71672dcb 100644
--- a/Documentation/technical/platform-support.adoc
+++ b/Documentation/technical/platform-support.adoc
@@ -33,6 +33,8 @@ meet the following minimum requirements:
* Has active security support (taking security releases of dependencies, etc)
+* Supports Rust and the toolchain version specified in link:rust-support.adoc[].
+
These requirements are a starting point, and not sufficient on their own for the
Git community to be enthusiastic about supporting your platform. Maintainers of
platforms which do meet these requirements can follow the steps below to make it
diff --git a/Documentation/technical/rust-support.adoc b/Documentation/technical/rust-support.adoc
new file mode 100644
index 0000000000..57a001fa2d
--- /dev/null
+++ b/Documentation/technical/rust-support.adoc
@@ -0,0 +1,142 @@
+Usage of Rust in Git
+====================
+
+Objective
+---------
+Introduce Rust into Git incrementally to improve security and maintainability.
+
+Background
+----------
+Git has historically been written primarily in C, with some portions in shell,
+Perl, or other languages. At the time it was originally written, this was
+important for portability and was a logical choice for software development.
+
+:0: link:https://security.googleblog.com/2024/09/eliminating-memory-safety-vulnerabilities-Android.html
+:1: link:https://www.cisa.gov/resources-tools/resources/product-security-bad-practices
+
+However, as time has progressed, we've seen an increased concern with memory
+safety vulnerabilities and the development of newer languages, such as Rust,
+that substantially limit or eliminate this class of vulnerabilities.
+Development in a variety of projects has found that memory safety
+vulnerabilities constitute about 70% of vulnerabilities of software in
+languages that are not memory safe. For instance, {0}[one survey of Android]
+found that memory safety vulnerabilities decreased from 76% to 24% over six
+years due to an increase in memory safe code. Similarly, the U.S. government
+is {1}[proposing to classify development in memory unsafe languages as a
+Product Security Bad Practice"].
+
+These risks are even more substantial when we consider the fact that Git is a
+network-facing service. Many organizations run Git servers internally or use a
+cloud-based forge, and the risk of accidental exposure or compromise of user
+data is substantial. It's important to ensure that Git, whether it's used
+locally or remotely, is robustly secure.
+
+In addition, C is a difficult language to write well and concisely. While it
+is of course possible to do anything with C, it lacks built-in support for
+niceties found in modern languages, such as hash tables, generics, typed
+errors, and automatic destruction, and most modern language offer shorter, more
+ergonomic syntax for expressing code. This is valuable functionality that can
+allow Git to be developed more rapidly, more easily, by more developers of a
+variety of levels, and with more confidence in the correctness of the code.
+
+For these reasons, adding Rust to Git is a sensible and prudent move that will
+allow us to improve the quality of the code and potentially attract new developers.
+
+Goals
+-----
+1. Git continues to build, run, and pass tests on a wide variety of operating
+ systems and architectures.
+2. Transition from C to Rust is incremental; that is, code can be ported as it
+ is convenient and Git does not need to transition all at once.
+3. Git continues to support older operating systems in conformance with the
+ platform support policy.
+
+Non-Goals
+---------
+1. Support for every possible operating system and architecture. Git already
+ has a platform support policy which defines what is supported and we already
+ exclude some operating systems for various reasons (e.g., lacking enough POSIX
+ tools to pass the test suite).
+2. Implementing C-only versions of Rust code or compiling a C-only Git. This
+ would be difficult to maintain and would not offer the ergonomic benefits we
+ desire.
+
+Design
+------
+Git will adopt Rust incrementally. This transition will start with the
+creation of a static library that can be linked into the existing Git binaries.
+At some point, we may wish to expose a dynamic library and compile the Git
+binaries themselves using Rust. Using an incremental approach allows us to
+determine as we go along how to structure our code in the best way for the
+project and avoids the need to make hard, potentially disruptive, transitions
+caused by porting a binary wholesale from one language to another that might
+introduce bugs.
+
+Crates like libc or rustix define types like c_long, but in ways that are not
+safe across platforms.
+From https://docs.rs/rustix/latest/rustix/ffi/type.c_long.html:
+
+ This type will always be i32 or i64. Most notably, many Linux-based
+ systems assume an i64, but Windows assumes i32. The C standard technically
+ only requires that this type be a signed integer that is at least 32 bits
+ and at least the size of an int, although in practice, no system would
+ have a long that is neither an i32 nor i64.
+
+Also, note that other locations, such as
+https://docs.rs/libc/latest/libc/type.c_long.html, just hardcode c_long as i64
+even though C may mean i32 on some platforms.
+
+As such, using the c_long type would give us portability issues, and
+perpetuate some of the bugs git has faced across platforms. Avoid using C's
+types (long, unsigned, char, etc.), and switch to unambiguous types (e.g. i32
+or i64) before trying to make C and Rust interoperate.
+
+Crates like libc and rustix may have also traditionally aided interoperability
+with older versions of Rust (e.g. when worrying about stat[64] system calls),
+but the Rust standard library in newer versions of Rust handle these concerns
+in a platform agnostic way. There may arise cases where we need to consider
+these crates, but for now we omit them.
+
+Tools like bindgen and cbindgen create C-styled unsafe Rust code rather than
+idiomatic Rust; where possible, we prefer to switch to idiomatic Rust. Any
+standard C library functions that are needed can be manually wrapped on the
+Rust side.
+
+Rust upstream releases every six weeks and only supports the latest stable
+release. While it is nice that upstream is active, we would like our software
+releases to have a lifespan exceeding six weeks. To allow compiling our code
+on a variety of systems, we will support the version of Rust in Debian stable,
+plus, for a year after a new Debian stable is released, the version in Debian
+oldstable.
+
+This provides an approximately three-year lifespan of support for a Rust
+release and allows us to support a variety of operating systems and
+architectures, including those for which Rust upstream does not build binaries.
+Debian stable is the benchmark distribution used by many Rust projects when
+determining supported Rust versions, and it is an extremely portable and
+popular free software operating system that is available to the public at no
+charge, which makes it a sensible choice for us as well.
+
+We may change this policy if the Rust project issues long-term support releases
+or the Rust community and distributors agree on releases to target as if they
+were long-term support releases.
+
+This version support policy necessitates that we be very careful about the
+dependencies we include, since many Rust projects support only the latest
+stable version. However, we typically have been careful about dependencies in
+the first place, so this should not be a major departure from existing policy,
+although it may be a change for some existing Rust developers.
+
+We will avoid including the `Cargo.lock` file in the repository and instead
+specify minimum dependency versions in the `Cargo.toml` file. We want to allow
+people to use newer versions of dependencies if necessary to support newer
+platforms without needing to force upgrades of dependencies on all users, and
+it provides additional flexibility for distribution maintainers.
+
+We do not plan to support beta or nightly versions of the Rust compiler. These
+versions may change rapidly and especially parts of the toolchain such as
+Clippy, the lint tool, can have false positives or add additional warnings with
+too great of a frequency to be supportable by the project. However, we do plan
+to support alternate compilers, such as the rust_codegen_gcc backend and gccrs
+when they are stable and support our desired release versions. This will
+provide greater support for more operating systems and architectures.
--
gitgitgadget
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 02/15] xdiff: introduce rust
2025-08-29 19:42 [PATCH 00/15] Introduce rust: In xdiff Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 01/15] doc: add a policy for using Rust brian m. carlson via GitGitGadget
@ 2025-08-29 19:42 ` Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 03/15] github workflows: install rust Ezekiel Newren via GitGitGadget
` (12 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Ezekiel Newren via GitGitGadget @ 2025-08-29 19:42 UTC (permalink / raw)
To: git; +Cc: Ezekiel Newren, Ezekiel Newren
From: Ezekiel Newren <ezekielnewren@gmail.com>
Upcoming patches will simplify xdiff, while also porting parts of it to
Rust. In preparation, add some stubs and setup the Rust build. For now,
it is easier to let cargo build rust and have make or meson merely link
against the static library that cargo builds. In line with ongoing
libification efforts, use multiple crates to allow more modularity on
the Rust side. xdiff is the crate that this series will focus on, but
we also introduce the interop crate for future patch series.
In order to facilitate interoperability between C and Rust, introduce C
definitions for Rust primitive types in git-compat-util.h.
Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
---
.gitignore | 3 +++
Makefile | 53 ++++++++++++++++++++++++++++----------
build_rust.sh | 57 +++++++++++++++++++++++++++++++++++++++++
git-compat-util.h | 17 ++++++++++++
meson.build | 52 +++++++++++++++++++++++++++++++------
rust/Cargo.toml | 6 +++++
rust/interop/Cargo.toml | 14 ++++++++++
rust/interop/src/lib.rs | 0
rust/xdiff/Cargo.toml | 15 +++++++++++
rust/xdiff/src/lib.rs | 0
10 files changed, 196 insertions(+), 21 deletions(-)
create mode 100755 build_rust.sh
create mode 100644 rust/Cargo.toml
create mode 100644 rust/interop/Cargo.toml
create mode 100644 rust/interop/src/lib.rs
create mode 100644 rust/xdiff/Cargo.toml
create mode 100644 rust/xdiff/src/lib.rs
diff --git a/.gitignore b/.gitignore
index 04c444404e..ff81e3580c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -254,3 +254,6 @@ Release/
/contrib/buildsystems/out
/contrib/libgit-rs/target
/contrib/libgit-sys/target
+/.idea/
+/rust/target/
+/rust/Cargo.lock
diff --git a/Makefile b/Makefile
index 70d1543b6b..1ec0c1ee66 100644
--- a/Makefile
+++ b/Makefile
@@ -919,6 +919,29 @@ TEST_SHELL_PATH = $(SHELL_PATH)
LIB_FILE = libgit.a
XDIFF_LIB = xdiff/lib.a
+
+EXTLIBS =
+
+ifeq ($(DEBUG), 1)
+ RUST_BUILD_MODE = debug
+else
+ RUST_BUILD_MODE = release
+endif
+
+RUST_TARGET_DIR = rust/target/$(RUST_BUILD_MODE)
+RUST_FLAGS_FOR_C = -L$(RUST_TARGET_DIR)
+
+.PHONY: compile_rust
+compile_rust:
+ ./build_rust.sh . $(RUST_BUILD_MODE) xdiff
+
+EXTLIBS += ./$(RUST_TARGET_DIR)/libxdiff.a
+
+UNAME_S := $(shell uname -s)
+ifeq ($(UNAME_S),Linux)
+ EXTLIBS += -ldl
+endif
+
REFTABLE_LIB = reftable/libreftable.a
GENERATED_H += command-list.h
@@ -1390,7 +1413,7 @@ UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
# xdiff and reftable libs may in turn depend on what is in libgit.a
GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)
-EXTLIBS =
+
GIT_USER_AGENT = git/$(GIT_VERSION)
@@ -2541,7 +2564,7 @@ git.sp git.s git.o: EXTRA_CPPFLAGS = \
'-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \
'-DGIT_INFO_PATH="$(infodir_relative_SQ)"'
-git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS)
+git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS) compile_rust
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
$(filter %.o,$^) $(LIBS)
@@ -2891,17 +2914,17 @@ headless-git.o: compat/win32/headless.c GIT-CFLAGS
headless-git$X: headless-git.o git.res GIT-LDFLAGS
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) -mwindows -o $@ $< git.res
-git-%$X: %.o GIT-LDFLAGS $(GITLIBS)
+git-%$X: %.o GIT-LDFLAGS $(GITLIBS) compile_rust
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
-git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS)
+git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS) compile_rust
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
$(IMAP_SEND_LDFLAGS) $(LIBS)
-git-http-fetch$X: http.o http-walker.o http-fetch.o GIT-LDFLAGS $(GITLIBS)
+git-http-fetch$X: http.o http-walker.o http-fetch.o GIT-LDFLAGS $(GITLIBS) compile_rust
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
$(CURL_LIBCURL) $(LIBS)
-git-http-push$X: http.o http-push.o GIT-LDFLAGS $(GITLIBS)
+git-http-push$X: http.o http-push.o GIT-LDFLAGS $(GITLIBS) compile_rust
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
$(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS)
@@ -2911,11 +2934,11 @@ $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)
ln -s $< $@ 2>/dev/null || \
cp $< $@
-$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS)
+$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS) compile_rust
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
$(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS)
-scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS)
+scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS) compile_rust
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
$(filter %.o,$^) $(LIBS)
@@ -2925,6 +2948,7 @@ $(LIB_FILE): $(LIB_OBJS)
$(XDIFF_LIB): $(XDIFF_OBJS)
$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
+
$(REFTABLE_LIB): $(REFTABLE_OBJS)
$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
@@ -3294,7 +3318,7 @@ perf: all
t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS)) $(UNIT_TEST_DIR)/test-lib.o
-t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS)
+t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) compile_rust
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
check-sha1:: t/helper/test-tool$X
@@ -3756,7 +3780,10 @@ cocciclean:
$(RM) -r .build/contrib/coccinelle
$(RM) contrib/coccinelle/*.cocci.patch
-clean: profile-clean coverage-clean cocciclean
+rustclean:
+ cd rust && cargo clean
+
+clean: profile-clean coverage-clean cocciclean rustclean
$(RM) -r .build $(UNIT_TEST_BIN)
$(RM) GIT-TEST-SUITES
$(RM) po/git.pot po/git-core.pot
@@ -3911,13 +3938,13 @@ FUZZ_CXXFLAGS ?= $(ALL_CFLAGS)
.PHONY: fuzz-all
fuzz-all: $(FUZZ_PROGRAMS)
-$(FUZZ_PROGRAMS): %: %.o oss-fuzz/dummy-cmd-main.o $(GITLIBS) GIT-LDFLAGS
+$(FUZZ_PROGRAMS): %: %.o oss-fuzz/dummy-cmd-main.o $(GITLIBS) GIT-LDFLAGS compile_rust
$(QUIET_LINK)$(FUZZ_CXX) $(FUZZ_CXXFLAGS) -o $@ $(ALL_LDFLAGS) \
-Wl,--allow-multiple-definition \
$(filter %.o,$^) $(filter %.a,$^) $(LIBS) $(LIB_FUZZING_ENGINE)
$(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o $(UNIT_TEST_OBJS) \
- $(GITLIBS) GIT-LDFLAGS
+ $(GITLIBS) GIT-LDFLAGS compile_rust
$(call mkdir_p_parent_template)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
$(filter %.o,$^) $(filter %.a,$^) $(LIBS)
@@ -3936,7 +3963,7 @@ $(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h $(UNIT_TEST_DIR)/gene
$(UNIT_TEST_DIR)/clar/clar.o: $(UNIT_TEST_DIR)/clar.suite
$(CLAR_TEST_OBJS): $(UNIT_TEST_DIR)/clar-decls.h
$(CLAR_TEST_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR)
-$(CLAR_TEST_PROG): $(UNIT_TEST_DIR)/clar.suite $(CLAR_TEST_OBJS) $(GITLIBS) GIT-LDFLAGS
+$(CLAR_TEST_PROG): $(UNIT_TEST_DIR)/clar.suite $(CLAR_TEST_OBJS) $(GITLIBS) GIT-LDFLAGS compile_rust
$(call mkdir_p_parent_template)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
diff --git a/build_rust.sh b/build_rust.sh
new file mode 100755
index 0000000000..192385a1d9
--- /dev/null
+++ b/build_rust.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+
+rustc -vV || exit $?
+cargo --version || exit $?
+
+dir_git_root=${0%/*}
+dir_build=$1
+rust_build_profile=$2
+crate=$3
+
+dir_rust=$dir_git_root/rust
+
+if [ "$dir_git_root" = "" ]; then
+ echo "did not specify the directory for the root of git"
+ exit 1
+fi
+
+if [ "$dir_build" = "" ]; then
+ echo "did not specify the build directory"
+ exit 1
+fi
+
+if [ "$rust_build_profile" = "" ]; then
+ echo "did not specify the rust_build_profile"
+ exit 1
+fi
+
+if [ "$rust_build_profile" = "release" ]; then
+ rust_args="--release"
+ export RUSTFLAGS=''
+elif [ "$rust_build_profile" = "debug" ]; then
+ rust_args=""
+ export RUSTFLAGS='-C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'
+else
+ echo "illegal rust_build_profile value $rust_build_profile"
+ exit 1
+fi
+
+cd $dir_rust && cargo clean && pwd && cargo build -p $crate $rust_args; cd $dir_git_root
+
+libfile="lib${crate}.a"
+if rustup show active-toolchain | grep windows-msvc; then
+ libfile="${crate}.lib"
+fi
+dst=$dir_build/$libfile
+
+if [ "$dir_git_root" != "$dir_build" ]; then
+ src=$dir_rust/target/$rust_build_profile/$libfile
+ if [ ! -f $src ]; then
+ echo >&2 "::error:: cannot find path of static library $src is not a file or does not exist"
+ exit 5
+ fi
+
+ rm $dst 2>/dev/null
+ mv $src $dst
+fi
diff --git a/git-compat-util.h b/git-compat-util.h
index 4678e21c4c..82dc99764a 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -196,6 +196,23 @@ static inline int is_xplatform_dir_sep(int c)
#include "compat/msvc.h"
#endif
+/* rust types */
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+typedef uint64_t u64;
+
+typedef int8_t i8;
+typedef int16_t i16;
+typedef int32_t i32;
+typedef int64_t i64;
+
+typedef float f32;
+typedef double f64;
+
+typedef size_t usize;
+typedef ptrdiff_t isize;
+
/* used on Mac OS X */
#ifdef PRECOMPOSE_UNICODE
#include "compat/precompose_utf8.h"
diff --git a/meson.build b/meson.build
index 596f5ac711..324f968338 100644
--- a/meson.build
+++ b/meson.build
@@ -267,6 +267,40 @@ version_gen_environment.set('GIT_DATE', get_option('build_date'))
version_gen_environment.set('GIT_USER_AGENT', get_option('user_agent'))
version_gen_environment.set('GIT_VERSION', get_option('version'))
+if get_option('optimization') in ['2', '3', 's', 'z']
+ rust_build_profile = 'release'
+else
+ rust_build_profile = 'debug'
+endif
+
+# Run `rustup show active-toolchain` and capture output
+rustup_out = run_command('rustup', 'show', 'active-toolchain',
+ check: true).stdout().strip()
+
+rust_crates = ['xdiff']
+rust_builds = []
+
+foreach crate : rust_crates
+ if rustup_out.contains('windows-msvc')
+ libfile = crate + '.lib'
+ else
+ libfile = 'lib' + crate + '.a'
+ endif
+
+ rust_builds += custom_target(
+ 'rust_build_'+crate,
+ output: libfile,
+ build_by_default: true,
+ build_always_stale: true,
+ command: [
+ meson.project_source_root() / 'build_rust.sh',
+ meson.current_build_dir(), rust_build_profile, crate,
+ ],
+ install: false,
+ )
+endforeach
+
+
compiler = meson.get_compiler('c')
libgit_sources = [
@@ -1678,14 +1712,16 @@ version_def_h = custom_target(
libgit_sources += version_def_h
libgit = declare_dependency(
- link_with: static_library('git',
- sources: libgit_sources,
- c_args: libgit_c_args + [
- '-DGIT_VERSION_H="' + version_def_h.full_path() + '"',
- ],
- dependencies: libgit_dependencies,
- include_directories: libgit_include_directories,
- ),
+ link_with: [
+ static_library('git',
+ sources: libgit_sources,
+ c_args: libgit_c_args + [
+ '-DGIT_VERSION_H="' + version_def_h.full_path() + '"',
+ ],
+ dependencies: libgit_dependencies,
+ include_directories: libgit_include_directories,
+ ),
+ ] + rust_builds,
compile_args: libgit_c_args,
dependencies: libgit_dependencies,
include_directories: libgit_include_directories,
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
new file mode 100644
index 0000000000..ed3d79d7f8
--- /dev/null
+++ b/rust/Cargo.toml
@@ -0,0 +1,6 @@
+[workspace]
+members = [
+ "xdiff",
+ "interop",
+]
+resolver = "2"
diff --git a/rust/interop/Cargo.toml b/rust/interop/Cargo.toml
new file mode 100644
index 0000000000..045e3b01cf
--- /dev/null
+++ b/rust/interop/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "interop"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+name = "interop"
+path = "src/lib.rs"
+## staticlib to generate xdiff.a for use by gcc
+## cdylib (optional) to generate xdiff.so for use by gcc
+## rlib is required by the rust unit tests
+crate-type = ["staticlib", "rlib"]
+
+[dependencies]
diff --git a/rust/interop/src/lib.rs b/rust/interop/src/lib.rs
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/rust/xdiff/Cargo.toml b/rust/xdiff/Cargo.toml
new file mode 100644
index 0000000000..eb7966aada
--- /dev/null
+++ b/rust/xdiff/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "xdiff"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+name = "xdiff"
+path = "src/lib.rs"
+## staticlib to generate xdiff.a for use by gcc
+## cdylib (optional) to generate xdiff.so for use by gcc
+## rlib is required by the rust unit tests
+crate-type = ["staticlib", "rlib"]
+
+[dependencies]
+interop = { path = "../interop" }
diff --git a/rust/xdiff/src/lib.rs b/rust/xdiff/src/lib.rs
new file mode 100644
index 0000000000..e69de29bb2
--
gitgitgadget
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 03/15] github workflows: install rust
2025-08-29 19:42 [PATCH 00/15] Introduce rust: In xdiff Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 01/15] doc: add a policy for using Rust brian m. carlson via GitGitGadget
2025-08-29 19:42 ` [PATCH 02/15] xdiff: introduce rust Ezekiel Newren via GitGitGadget
@ 2025-08-29 19:42 ` Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 04/15] win+Meson: do allow linking with the Rust-built xdiff Johannes Schindelin via GitGitGadget
` (11 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Ezekiel Newren via GitGitGadget @ 2025-08-29 19:42 UTC (permalink / raw)
To: git; +Cc: Ezekiel Newren, Ezekiel Newren
From: Ezekiel Newren <ezekielnewren@gmail.com>
Prefer using actions-rs/toolchain@v1 where possible to install rustup,
but for docker targets use a script to install rustup. Consolidate the
Rust toolchain definitions in main.yaml. Use install-rust-toolchain.sh
to ensure the correct toolchain is used. Five overrides are used in
main.yaml:
* On Windows: Rust didn't resolve the bcrypt library on Windows
correctly until version 1.78.0. Also since rustup mis-identifies
the Rust toolchain, the Rust target triple must be set to
x86_64-pc-windows-gnu for make (win build), and
x86_64-pc-windows-msvc for meson (win+Meson build).
* On musl: libc differences, such as ftruncate64 vs ftruncate, were
not accounted for until Rust version 1.72.0. No older version of
Rust will work on musl for our needs.
* In a 32-bit docker container running on a 64-bit host, we need to
override the Rust target triple. This is because rustup asks the
kernel for the bitness of the system and it says 64, even though
the container is 32-bit. This also allows us to remove the
BITNESS environment variable in ci/lib.sh.
The logic for selecting library names was initially provided in a patch
from Johannes, but was reworked and squashed into this commit.
Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
---
.github/workflows/main.yml | 60 +++++++++++++++++++++++++++++++++++-
ci/install-dependencies.sh | 14 ++++-----
ci/install-rust-toolchain.sh | 30 ++++++++++++++++++
ci/install-rustup.sh | 25 +++++++++++++++
ci/lib.sh | 1 +
ci/make-test-artifacts.sh | 9 ++++++
ci/run-build-and-tests.sh | 13 ++++++++
7 files changed, 144 insertions(+), 8 deletions(-)
create mode 100755 ci/install-rust-toolchain.sh
create mode 100755 ci/install-rustup.sh
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 7dbf9f7f12..ac1d583ab2 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -26,6 +26,13 @@ jobs:
outputs:
enabled: ${{ steps.check-ref.outputs.enabled }}${{ steps.skip-if-redundant.outputs.enabled }}
skip_concurrent: ${{ steps.check-ref.outputs.skip_concurrent }}
+ rust_version_minimum: 1.61.0
+ rust_version_windows: 1.78.0
+ rust_version_musl: 1.72.0
+ ## the rust target is inferred by rustup unless specified
+ rust_target_windows_make: x86_64-pc-windows-gnu
+ rust_target_windows_meson: x86_64-pc-windows-msvc
+ rust_target_32bit_linux: i686-unknown-linux-gnu
steps:
- name: try to clone ci-config branch
run: |
@@ -108,12 +115,26 @@ jobs:
needs: ci-config
if: needs.ci-config.outputs.enabled == 'yes'
runs-on: windows-latest
+ env:
+ CARGO_HOME: "/c/Users/runneradmin/.cargo"
concurrency:
group: windows-build-${{ github.ref }}
cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }}
steps:
- uses: actions/checkout@v4
- uses: git-for-windows/setup-git-for-windows-sdk@v1
+ - name: Install rustup via github actions
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ profile: minimal
+ override: false
+ - name: Install Rust toolchain
+ shell: bash
+ env:
+ RUST_VERSION: ${{ needs.ci-config.outputs.rust_version_windows }}
+ RUST_TARGET: ${{ needs.ci-config.outputs.rust_target_windows_make }}
+ run: ci/install-rust-toolchain.sh
- name: build
shell: bash
env:
@@ -254,12 +275,26 @@ jobs:
needs: ci-config
if: needs.ci-config.outputs.enabled == 'yes'
runs-on: windows-latest
+ env:
+ CARGO_HOME: "/c/Users/runneradmin/.cargo"
concurrency:
group: windows-meson-build-${{ github.ref }}
cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
+ - name: Install rustup via github actions
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ profile: minimal
+ override: false
+ - name: Install Rust toolchain
+ shell: bash
+ env:
+ RUST_VERSION: ${{ needs.ci-config.outputs.rust_version_windows }}
+ RUST_TARGET: ${{ needs.ci-config.outputs.rust_target_windows_meson }}
+ run: ci/install-rust-toolchain.sh
- name: Set up dependencies
shell: pwsh
run: pip install meson ninja
@@ -329,11 +364,24 @@ jobs:
jobname: ${{matrix.vector.jobname}}
CI_JOB_IMAGE: ${{matrix.vector.pool}}
TEST_OUTPUT_DIRECTORY: ${{github.workspace}}/t
+ CARGO_HOME: "/Users/runner/.cargo"
runs-on: ${{matrix.vector.pool}}
steps:
- uses: actions/checkout@v4
- run: ci/install-dependencies.sh
- - run: ci/run-build-and-tests.sh
+ - name: Install rustup via github actions
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ profile: minimal
+ override: false
+ - name: Install Rust toolchain
+ shell: bash
+ env:
+ RUST_VERSION: ${{ needs.ci-config.outputs.rust_version_minimum }}
+ run: ci/install-rust-toolchain.sh
+ - name: Run build and tests
+ run: ci/run-build-and-tests.sh
- name: print test failures
if: failure() && env.FAILED_TEST_ARTIFACTS != ''
run: ci/print-test-failures.sh
@@ -393,9 +441,11 @@ jobs:
cc: gcc
- jobname: linux-musl-meson
image: alpine:latest
+ rust_version_override: ${{ needs.ci-config.outputs.rust_version_musl }}
# Supported until 2025-04-02.
- jobname: linux32
image: i386/ubuntu:focal
+ rust_target_override: ${{ needs.ci-config.outputs.rust_target_32bit_linux }}
- jobname: pedantic
image: fedora:latest
# A RHEL 8 compatible distro. Supported until 2029-05-31.
@@ -409,6 +459,7 @@ jobs:
CC: ${{matrix.vector.cc}}
CI_JOB_IMAGE: ${{matrix.vector.image}}
CUSTOM_PATH: /custom
+ CARGO_HOME: /home/builder/.cargo
runs-on: ubuntu-latest
container: ${{matrix.vector.image}}
steps:
@@ -433,6 +484,13 @@ jobs:
- run: ci/install-dependencies.sh
- run: useradd builder --create-home
- run: chown -R builder .
+ - name: Install rustup via script
+ run: sudo --preserve-env --set-home --user=builder ci/install-rustup.sh
+ - name: Install Rust toolchain
+ env:
+ RUST_VERSION: ${{ matrix.vector.rust_version_override || needs.ci-config.outputs.rust_version_minimum }}
+ RUST_TARGET: ${{ matrix.vector.rust_target_override || '' }}
+ run: sudo --preserve-env --set-home --user=builder ci/install-rust-toolchain.sh
- run: sudo --preserve-env --set-home --user=builder ci/run-build-and-tests.sh
- name: print test failures
if: failure() && env.FAILED_TEST_ARTIFACTS != ''
diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh
index d061a47293..7801075821 100755
--- a/ci/install-dependencies.sh
+++ b/ci/install-dependencies.sh
@@ -24,14 +24,14 @@ fi
case "$distro" in
alpine-*)
- apk add --update shadow sudo meson ninja-build gcc libc-dev curl-dev openssl-dev expat-dev gettext \
+ apk add --update shadow sudo meson ninja-build gcc libc-dev curl curl-dev openssl-dev expat-dev gettext \
zlib-ng-dev pcre2-dev python3 musl-libintl perl-utils ncurses \
apache2 apache2-http2 apache2-proxy apache2-ssl apache2-webdav apr-util-dbd_sqlite3 \
bash cvs gnupg perl-cgi perl-dbd-sqlite perl-io-tty >/dev/null
;;
fedora-*|almalinux-*)
dnf -yq update >/dev/null &&
- dnf -yq install shadow-utils sudo make gcc findutils diffutils perl python3 gawk gettext zlib-devel expat-devel openssl-devel curl-devel pcre2-devel >/dev/null
+ dnf -yq install shadow-utils sudo make gcc findutils diffutils perl python3 gawk gettext zlib-devel expat-devel openssl-devel curl curl-devel pcre2-devel >/dev/null
;;
ubuntu-*|i386/ubuntu-*|debian-*)
# Required so that apt doesn't wait for user input on certain packages.
@@ -55,8 +55,8 @@ ubuntu-*|i386/ubuntu-*|debian-*)
sudo apt-get -q update
sudo apt-get -q -y install \
$LANGUAGES apache2 cvs cvsps git gnupg $SVN \
- make libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \
- tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \
+ make libssl-dev curl libcurl4-openssl-dev libexpat-dev wget sudo default-jre \
+ tcl tk gettext zlib1g zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \
libemail-valid-perl libio-pty-perl libio-socket-ssl-perl libnet-smtp-ssl-perl libdbd-sqlite3-perl libcgi-pm-perl \
libsecret-1-dev libpcre2-dev meson ninja-build pkg-config \
${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE
@@ -121,13 +121,13 @@ ClangFormat)
;;
StaticAnalysis)
sudo apt-get -q update
- sudo apt-get -q -y install coccinelle libcurl4-openssl-dev libssl-dev \
+ sudo apt-get -q -y install coccinelle curl libcurl4-openssl-dev libssl-dev \
libexpat-dev gettext make
;;
sparse)
sudo apt-get -q update -q
- sudo apt-get -q -y install libssl-dev libcurl4-openssl-dev \
- libexpat-dev gettext zlib1g-dev sparse
+ sudo apt-get -q -y install libssl-dev curl libcurl4-openssl-dev \
+ libexpat-dev gettext zlib1g zlib1g-dev sparse
;;
Documentation)
sudo apt-get -q update
diff --git a/ci/install-rust-toolchain.sh b/ci/install-rust-toolchain.sh
new file mode 100755
index 0000000000..06a29c4cfa
--- /dev/null
+++ b/ci/install-rust-toolchain.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+if [ "$CARGO_HOME" = "" ]; then
+ echo >&2 "::error:: CARGO_HOME is not set"
+ exit 2
+fi
+export PATH="$CARGO_HOME/bin:$PATH"
+rustup -vV || exit $?
+
+## Enforce the correct Rust toolchain
+rustup override unset || true
+
+## install a specific version of rust
+if [ "$RUST_TARGET" != "" ]; then
+ rustup default --force-non-host "$RUST_VERSION-$RUST_TARGET" || exit $?
+else
+ rustup default "$RUST_VERSION" || exit $?
+fi
+
+rustc -vV || exit $?
+
+RE_RUST_TARGET="$RUST_TARGET"
+if [ "$RUST_TARGET" = "" ]; then
+ RE_RUST_TARGET="[^ ]+"
+fi
+
+if ! rustup show active-toolchain | grep -E "^$RUST_VERSION-$RE_RUST_TARGET \(default\)$"; then
+ echo >&2 "::error:: wrong Rust toolchain, active-toolchain: $(rustup show active-toolchain)"
+ exit 3
+fi
diff --git a/ci/install-rustup.sh b/ci/install-rustup.sh
new file mode 100755
index 0000000000..0036231aee
--- /dev/null
+++ b/ci/install-rustup.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+## github workflows actions-rs/toolchain@v1 doesn't work for docker
+## targets. This script should only be used if the ci pipeline
+## doesn't support installing rust on a particular target.
+
+if [ "$(id -u)" -eq 0 ]; then
+ echo >&2 "::warning:: installing rust as root"
+fi
+
+if [ "$CARGO_HOME" = "" ]; then
+ echo >&2 "::error:: CARGO_HOME is not set"
+ exit 2
+fi
+
+export RUSTUP_HOME=$CARGO_HOME
+
+## install rustup
+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain none -y
+if [ ! -f $CARGO_HOME/env ]; then
+ echo "PATH=$CARGO_HOME/bin:\$PATH" > $CARGO_HOME/env
+fi
+. $CARGO_HOME/env
+
+rustup -vV
diff --git a/ci/lib.sh b/ci/lib.sh
index f561884d40..a7992b22fd 100755
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -1,5 +1,6 @@
# Library of functions shared by all CI scripts
+
if test true = "$GITHUB_ACTIONS"
then
begin_group () {
diff --git a/ci/make-test-artifacts.sh b/ci/make-test-artifacts.sh
index 74141af0cc..e37ed7030c 100755
--- a/ci/make-test-artifacts.sh
+++ b/ci/make-test-artifacts.sh
@@ -7,6 +7,15 @@ mkdir -p "$1" # in case ci/lib.sh decides to quit early
. ${0%/*}/lib.sh
+## ensure rustup is in the PATH variable
+if [ "$CARGO_HOME" = "" ]; then
+ echo >&2 "::error:: CARGO_HOME is not set"
+ exit 2
+fi
+export PATH="$CARGO_HOME/bin:$PATH"
+
+rustc -vV
+
group Build make artifacts-tar ARTIFACTS_DIRECTORY="$1"
check_unignored_build_artifacts
diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh
index 01823fd0f1..22b61e2812 100755
--- a/ci/run-build-and-tests.sh
+++ b/ci/run-build-and-tests.sh
@@ -5,6 +5,15 @@
. ${0%/*}/lib.sh
+## ensure rustup is in the PATH variable
+if [ "$CARGO_HOME" = "" ]; then
+ echo >&2 "::error:: CARGO_HOME is not set"
+ exit 2
+fi
+. $CARGO_HOME/env
+
+rustc -vV || exit $?
+
run_tests=t
case "$jobname" in
@@ -72,5 +81,9 @@ case "$jobname" in
;;
esac
+if [ -d "$CARGO_HOME" ]; then
+ rm -rf $CARGO_HOME
+fi
+
check_unignored_build_artifacts
save_good_tree
--
gitgitgadget
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 04/15] win+Meson: do allow linking with the Rust-built xdiff
2025-08-29 19:42 [PATCH 00/15] Introduce rust: In xdiff Ezekiel Newren via GitGitGadget
` (2 preceding siblings ...)
2025-08-29 19:42 ` [PATCH 03/15] github workflows: install rust Ezekiel Newren via GitGitGadget
@ 2025-08-29 19:42 ` Johannes Schindelin via GitGitGadget
2025-08-29 19:42 ` [PATCH 05/15] github workflows: upload Cargo.lock Ezekiel Newren via GitGitGadget
` (10 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2025-08-29 19:42 UTC (permalink / raw)
To: git; +Cc: Ezekiel Newren, Johannes Schindelin
From: Johannes Schindelin <johannes.schindelin@gmx.de>
When linking against the Rust-built `xdiff`, there is now a new required
dependency: Without _also_ linking to the system library `userenv`, the
compile would fail with this error message:
xdiff.lib(std-c85e9beb7923f636.std.df32d1bc89881d89-cgu.0.rcgu.o) :
error LNK2019: unresolved external symbol __imp_GetUserProfileDirectoryW
referenced in function _ZN3std3env8home_dir17hfd1c3b6676cd78f6E
Therefore, just like we do in case of Makefile-based builds on Windows,
we now also link to that library when building with Meson.
Note that if we only have Rust depend upon libuserenv then at link time
GCC would complain about:
undefined reference to `GetUserProfileDirectoryW'
Apparently there is _some_ closure that gets compiled in that requires
this function, and that in turn forces Git to link to libuserenv.
This is a new requirement, and therefore has not been made part of the
"minimal Git for Windows SDK".
In the near future, I intend to include it, but for now let's just
ensure that the file is added manually if it is missing.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
[en: Squashed a few of Johannes's patches, and moved lib userenv
handling from an earlier patch]
Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
---
.github/workflows/main.yml | 8 ++++++++
config.mak.uname | 4 ++++
meson.build | 1 +
3 files changed, 13 insertions(+)
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index ac1d583ab2..ff2f90d122 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -123,6 +123,14 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: git-for-windows/setup-git-for-windows-sdk@v1
+ - name: ensure that libuserenv.a is present
+ shell: bash
+ run: |
+ cd /mingw64/lib && {
+ test -f libuserenv.a ||
+ /c/Program\ Files/Git/mingw64/bin/curl -Lo libuserenv.a \
+ https://github.com/git-for-windows/git-sdk-64/raw/HEAD/mingw64/lib/libuserenv.a
+ }
- name: Install rustup via github actions
uses: actions-rs/toolchain@v1
with:
diff --git a/config.mak.uname b/config.mak.uname
index 3e26bb074a..6805e3778a 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -740,6 +740,10 @@ ifeq ($(uname_S),MINGW)
COMPAT_CFLAGS += -D_USE_32BIT_TIME_T
BASIC_LDFLAGS += -Wl,--large-address-aware
endif
+
+ # Unfortunately now needed because of Rust
+ EXTLIBS += -luserenv
+
CC = gcc
COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY \
-fstack-protector-strong
diff --git a/meson.build b/meson.build
index 324f968338..5aa9901bfc 100644
--- a/meson.build
+++ b/meson.build
@@ -1267,6 +1267,7 @@ elif host_machine.system() == 'windows'
]
libgit_dependencies += compiler.find_library('ntdll')
+ libgit_dependencies += compiler.find_library('userenv')
libgit_include_directories += 'compat/win32'
if compiler.get_id() == 'msvc'
libgit_include_directories += 'compat/vcbuild/include'
--
gitgitgadget
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 05/15] github workflows: upload Cargo.lock
2025-08-29 19:42 [PATCH 00/15] Introduce rust: In xdiff Ezekiel Newren via GitGitGadget
` (3 preceding siblings ...)
2025-08-29 19:42 ` [PATCH 04/15] win+Meson: do allow linking with the Rust-built xdiff Johannes Schindelin via GitGitGadget
@ 2025-08-29 19:42 ` Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 06/15] ivec: create a vector type that is interoperable between C and Rust Ezekiel Newren via GitGitGadget
` (9 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Ezekiel Newren via GitGitGadget @ 2025-08-29 19:42 UTC (permalink / raw)
To: git; +Cc: Ezekiel Newren, Ezekiel Newren
From: Ezekiel Newren <ezekielnewren@gmail.com>
Make each ci workflow upload its Cargo.lock file as a build artifact so
that we can audit build dependencies.
Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
---
.github/workflows/main.yml | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index ff2f90d122..cdd57b4ee1 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -156,6 +156,11 @@ jobs:
with:
name: windows-artifacts
path: artifacts
+ - name: upload Cargo.lock
+ uses: actions/upload-artifact@v4
+ with:
+ name: cargo-lock-windows
+ path: rust/Cargo.lock
windows-test:
name: win test
runs-on: windows-latest
@@ -317,6 +322,11 @@ jobs:
with:
name: windows-meson-artifacts
path: build
+ - name: Upload Cargo.lock
+ uses: actions/upload-artifact@v4
+ with:
+ name: cargo-lock-windows-meson
+ path: rust/Cargo.lock
windows-meson-test:
name: win+Meson test
runs-on: windows-latest
@@ -399,6 +409,11 @@ jobs:
with:
name: failed-tests-${{matrix.vector.jobname}}
path: ${{env.FAILED_TEST_ARTIFACTS}}
+ - name: Upload Cargo.lock
+ uses: actions/upload-artifact@v4
+ with:
+ name: cargo-lock-${{matrix.vector.jobname}}
+ path: rust/Cargo.lock
fuzz-smoke-test:
name: fuzz smoke test
needs: ci-config
@@ -509,6 +524,11 @@ jobs:
with:
name: failed-tests-${{matrix.vector.jobname}}
path: ${{env.FAILED_TEST_ARTIFACTS}}
+ - name: Upload Cargo.lock
+ uses: actions/upload-artifact@v4
+ with:
+ name: cargo-lock-${{matrix.vector.jobname}}
+ path: rust/Cargo.lock
static-analysis:
needs: ci-config
if: needs.ci-config.outputs.enabled == 'yes'
--
gitgitgadget
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 06/15] ivec: create a vector type that is interoperable between C and Rust
2025-08-29 19:42 [PATCH 00/15] Introduce rust: In xdiff Ezekiel Newren via GitGitGadget
` (4 preceding siblings ...)
2025-08-29 19:42 ` [PATCH 05/15] github workflows: upload Cargo.lock Ezekiel Newren via GitGitGadget
@ 2025-08-29 19:42 ` Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 07/15] xdiff/xprepare: remove superfluous forward declarations Ezekiel Newren via GitGitGadget
` (8 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Ezekiel Newren via GitGitGadget @ 2025-08-29 19:42 UTC (permalink / raw)
To: git; +Cc: Ezekiel Newren, Ezekiel Newren
From: Ezekiel Newren <ezekielnewren@gmail.com>
Trying to use Rust's Vec in C, or git's ALLOC_GROW() macros (via
wrapper functions) in Rust is painful because:
* C doing vector things the Rust way would require wrapper functions,
and Rust doing vector things the C way would require wrapper
functions, so ivec was created to ensure a consistent contract
between the 2 languages for how to manipulate a vector.
* Currently, Rust defines its own 'Vec' type that is generic, but its
memory allocator and struct layout weren't designed for
interoperability with C (or any language for that matter), meaning
that the C side cannot push to or expand a 'Vec' without defining
wrapper functions in Rust that C can call. Without special care,
the two languages might use different allocators (malloc/free on
the C side, and possibly something else in Rust), which would make
it difficult for a function in one language to free elements
allocated by a call from a function in the other language.
* Similarly, git defines ALLOC_GROW() and related macros in
git-compat-util.h. While we could add functions allowing Rust to
invoke something similar to those macros, passing three variables
(pointer, length, allocated_size) instead of a single variable
(vector) across the language boundary requires more cognitive
overhead for readers to keep track of and makes it easier to make
mistakes. Further, for low-level components that we want to
eventually convert to pure Rust, such triplets would feel very out
of place.
To address these issue, introduce a new type, ivec -- short for
interoperable vector. (We refer to it as 'ivec' generally, though on
the Rust side the struct is called IVec to match Rust style.) This new
type is specifically designed for FFI purposes, so that both languages
handle the vector in the same way, though it could be used on either
side independently. This type is designed such that it can easily be
replaced by a standard Rust 'Vec' once interoperability is no longer a
concern.
One particular item to note is that Git's macros to handle vec
operations infer the amount that a vec needs to grow from the size of
a pointer, but that makes it somewhat specific to the macros used in C.
To avoid defining every ivec function as a macro I opted to also
include an element_size field that allows concrete functions like
push() to know how much to grow the memory. This element_size also
helps in verifying that the ivec is correct when passing from C to
Rust.
Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
---
Makefile | 14 +-
interop/ivec.c | 180 ++++++++++++++
interop/ivec.h | 60 +++++
meson.build | 1 +
rust/interop/src/ivec.rs | 516 +++++++++++++++++++++++++++++++++++++++
rust/interop/src/lib.rs | 10 +
6 files changed, 778 insertions(+), 3 deletions(-)
create mode 100644 interop/ivec.c
create mode 100644 interop/ivec.h
create mode 100644 rust/interop/src/ivec.rs
diff --git a/Makefile b/Makefile
index 1ec0c1ee66..46e940c96d 100644
--- a/Makefile
+++ b/Makefile
@@ -672,6 +672,7 @@ BUILTIN_OBJS =
BUILT_INS =
COMPAT_CFLAGS =
COMPAT_OBJS =
+INTEROP_OBJS =
XDIFF_OBJS =
GENERATED_H =
EXTRA_CPPFLAGS =
@@ -918,6 +919,7 @@ export PYTHON_PATH
TEST_SHELL_PATH = $(SHELL_PATH)
LIB_FILE = libgit.a
+INTEROP_LIB = interop/lib.a
XDIFF_LIB = xdiff/lib.a
EXTLIBS =
@@ -1412,7 +1414,7 @@ UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o
UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
# xdiff and reftable libs may in turn depend on what is in libgit.a
-GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)
+GITLIBS = common-main.o $(LIB_FILE) $(INTEROP_LIB) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)
GIT_USER_AGENT = git/$(GIT_VERSION)
@@ -2747,6 +2749,8 @@ reconfigure config.mak.autogen: config.status
.PHONY: reconfigure # This is a convenience target.
endif
+INTEROP_OBJS += interop/ivec.o
+
XDIFF_OBJS += xdiff/xdiffi.o
XDIFF_OBJS += xdiff/xemit.o
XDIFF_OBJS += xdiff/xhistogram.o
@@ -2791,6 +2795,7 @@ OBJECTS += $(GIT_OBJS)
OBJECTS += $(SCALAR_OBJS)
OBJECTS += $(PROGRAM_OBJS)
OBJECTS += $(TEST_OBJS)
+OBJECTS += $(INTEROP_OBJS)
OBJECTS += $(XDIFF_OBJS)
OBJECTS += $(FUZZ_OBJS)
OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)
@@ -2945,7 +2950,10 @@ scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS) compile_rust
$(LIB_FILE): $(LIB_OBJS)
$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
-$(XDIFF_LIB): $(XDIFF_OBJS)
+$(INTEROP_LIB): $(INTEROP_OBJS)
+ $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
+
+$(XDIFF_LIB): $(INTEROP_OBJS) $(XDIFF_OBJS)
$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
@@ -3790,7 +3798,7 @@ clean: profile-clean coverage-clean cocciclean rustclean
$(RM) git.rc git.res
$(RM) $(OBJECTS)
$(RM) headless-git.o
- $(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB)
+ $(RM) $(LIB_FILE) $(INTEROP_LIB) $(XDIFF_LIB) $(REFTABLE_LIB)
$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS)
$(RM) $(TEST_PROGRAMS)
$(RM) $(FUZZ_PROGRAMS)
diff --git a/interop/ivec.c b/interop/ivec.c
new file mode 100644
index 0000000000..1c4bf20707
--- /dev/null
+++ b/interop/ivec.c
@@ -0,0 +1,180 @@
+#include "ivec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static void ivec_set_capacity(void *self_, usize new_capacity)
+{
+ struct rawivec *self = self_;
+
+ if (new_capacity == 0)
+ FREE_AND_NULL(self->ptr);
+ else
+ self->ptr = xrealloc(self->ptr, new_capacity * self->element_size);
+ self->capacity = new_capacity;
+}
+
+void ivec_init(void *self_, usize element_size)
+{
+ struct rawivec *self = self_;
+
+ self->ptr = NULL;
+ self->length = 0;
+ self->capacity = 0;
+ self->element_size = element_size;
+}
+
+/*
+ * MUST CALL IVEC_INIT() FIRST!!!
+ * This function will free the ivec, set self.capacity and self.length
+ * to the specified capacity, and then calloc self.capacity number of
+ * elements.
+ */
+void ivec_zero(void *self_, usize capacity)
+{
+ struct rawivec *self = self_;
+
+ if (self->ptr)
+ FREE_AND_NULL(self->ptr);
+ self->capacity = self->length = capacity;
+ self->ptr = xcalloc(self->capacity, self->element_size);
+}
+
+void ivec_clear(void *self_)
+{
+ struct rawivec *self = self_;
+
+ self->length = 0;
+}
+
+void ivec_reserve_exact(void *self_, usize additional)
+{
+ struct rawivec *self = self_;
+ usize new_capacity = self->capacity + additional;
+
+ ivec_set_capacity(self, new_capacity);
+}
+
+void ivec_reserve(void *self_, usize additional)
+{
+ struct rawivec *self = self_;
+ usize growby = 128;
+
+ if (self->capacity > growby) {
+ growby = self->capacity;
+ }
+ if (additional > growby) {
+ growby = additional;
+ }
+ ivec_reserve_exact(self, growby);
+}
+
+void ivec_shrink_to_fit(void *self_)
+{
+ struct rawivec *self = self_;
+
+ ivec_set_capacity(self_, self->length);
+}
+
+void ivec_resize(void *self_, usize new_length, void *default_value)
+{
+ struct rawivec *self = self_;
+ isize additional = (isize) (new_length - self->capacity);
+
+ if (additional > 0) {
+ ivec_reserve(self_, additional);
+ }
+
+ for (usize i = self->length; i < new_length; i++) {
+ void *dst = (u8 *)self->ptr + (self->length + i) * self->element_size;
+ memcpy(dst, default_value, self->element_size);
+ }
+ self->length = new_length;
+}
+
+void ivec_push(void *self_, void *value)
+{
+ struct rawivec *self = self_;
+ u8 *dst;
+
+ if (self->length == self->capacity) {
+ ivec_reserve(self_, 1);
+ }
+ dst = (u8 *)self->ptr + self->length * self->element_size;
+ memcpy(dst, value, self->element_size);
+ self->length++;
+}
+
+void ivec_extend_from_slice(void *self_, void const *ptr, usize size)
+{
+ struct rawivec *self = self_;
+ u8 *dst;
+
+ if (size == 0)
+ return;
+
+ if (self->length + size > self->capacity) {
+ ivec_reserve(self_, self->capacity - self->length + size);
+ }
+ dst = (u8 *)self->ptr + self->length * self->element_size;
+ memcpy(dst, ptr, size * self->element_size);
+ self->length += size;
+}
+
+bool ivec_equal(void *self_, void *other)
+{
+ struct rawivec *lhs = self_;
+ struct rawivec *rhs = other;
+
+ if (lhs->element_size != rhs->element_size) {
+ return false;
+ }
+
+ if (lhs->length != rhs->length) {
+ return false;
+ }
+
+ for (usize i = 0; i < lhs->length; i++) {
+ void *left = (u8 *)lhs->ptr + i * lhs->element_size;
+ void *right = (u8 *)rhs->ptr + i * rhs->element_size;
+ if (memcmp(left, right, lhs->element_size) != 0) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+void ivec_free(void *self_)
+{
+ struct rawivec *self = self_;
+
+ FREE_AND_NULL(self->ptr);
+ self->length = 0;
+ self->capacity = 0;
+ /* don't modify self->element_size */
+}
+
+void ivec_move(void *source, void *destination)
+{
+ struct rawivec *src = source;
+ struct rawivec *dst = destination;
+
+ if (src->element_size != dst->element_size)
+ BUG("mismatched element_size");
+
+ ivec_free(destination);
+ dst->ptr = src->ptr;
+ dst->length = src->length;
+ dst->capacity = src->capacity;
+
+ src->ptr = NULL;
+ src->length = 0;
+ src->capacity = 0;
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/interop/ivec.h b/interop/ivec.h
new file mode 100644
index 0000000000..ed3afff944
--- /dev/null
+++ b/interop/ivec.h
@@ -0,0 +1,60 @@
+#ifndef IVEC_H
+#define IVEC_H
+
+#include <git-compat-util.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct rawivec {
+ void *ptr;
+ usize length;
+ usize capacity;
+ usize element_size;
+};
+
+#define DEFINE_IVEC_TYPE(type, suffix) \
+struct ivec_##suffix { \
+ type *ptr; \
+ size_t length; \
+ size_t capacity; \
+ size_t element_size; \
+}
+
+#define IVEC_INIT(variable) ivec_init(&(variable), sizeof(*(variable).ptr))
+
+DEFINE_IVEC_TYPE(u8, u8);
+DEFINE_IVEC_TYPE(u16, u16);
+DEFINE_IVEC_TYPE(u32, u32);
+DEFINE_IVEC_TYPE(u64, u64);
+
+DEFINE_IVEC_TYPE(i8, i8);
+DEFINE_IVEC_TYPE(i16, i16);
+DEFINE_IVEC_TYPE(i32, i32);
+DEFINE_IVEC_TYPE(i64, i64);
+
+DEFINE_IVEC_TYPE(f32, f32);
+DEFINE_IVEC_TYPE(f64, f64);
+
+DEFINE_IVEC_TYPE(usize, usize);
+DEFINE_IVEC_TYPE(isize, isize);
+
+void ivec_init(void *self_, usize element_size);
+void ivec_zero(void *self_, usize capacity);
+void ivec_clear(void *self_);
+void ivec_reserve_exact(void *self_, usize additional);
+void ivec_reserve(void *self_, usize additional);
+void ivec_shrink_to_fit(void *self_);
+void ivec_resize(void *self_, usize new_length, void *default_value);
+void ivec_push(void *self_, void *value);
+void ivec_extend_from_slice(void *self_, void const *ptr, usize size);
+bool ivec_equal(void *self_, void *other);
+void ivec_free(void *self_);
+void ivec_move(void *source, void *destination);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //IVEC_H
diff --git a/meson.build b/meson.build
index 5aa9901bfc..fc7c133f79 100644
--- a/meson.build
+++ b/meson.build
@@ -395,6 +395,7 @@ libgit_sources = [
'hex-ll.c',
'hook.c',
'ident.c',
+ 'interop/ivec.c',
'json-writer.c',
'kwset.c',
'levenshtein.c',
diff --git a/rust/interop/src/ivec.rs b/rust/interop/src/ivec.rs
new file mode 100644
index 0000000000..ba9393a82b
--- /dev/null
+++ b/rust/interop/src/ivec.rs
@@ -0,0 +1,516 @@
+use crate::*;
+use core::mem::{align_of, size_of};
+use std::fmt::{Debug, Formatter};
+use std::ops::{Index, IndexMut};
+
+#[repr(C)]
+pub struct IVec<T> {
+ ptr: *mut T,
+ length: usize,
+ capacity: usize,
+ element_size: usize,
+}
+
+impl<T> Default for IVec<T> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl<T> Drop for IVec<T> {
+ fn drop(&mut self) {
+ self._set_capacity(0);
+ }
+}
+
+impl<T: Clone> Clone for IVec<T> {
+ fn clone(&self) -> Self {
+ let mut copy = Self::new();
+ copy.reserve_exact(self.len());
+ for i in 0..self.len() {
+ copy.push(self[i].clone());
+ }
+
+ copy
+ }
+}
+
+impl<T: PartialEq> PartialEq for IVec<T> {
+ fn eq(&self, other: &Self) -> bool {
+ if self.len() != other.len() {
+ return false;
+ }
+
+ let lhs = self.as_slice();
+ let rhs = &other.as_slice()[..lhs.len()];
+ for i in 0..lhs.len() {
+ if lhs[i] != rhs[i] {
+ return false;
+ }
+ }
+
+ true
+ }
+}
+
+impl<T: PartialEq> Eq for IVec<T> {}
+
+/*
+ * constructors
+ */
+impl<T> IVec<T> {
+ pub fn new() -> Self {
+ Self {
+ ptr: std::ptr::null_mut(),
+ length: 0,
+ capacity: 0,
+ element_size: size_of::<T>(),
+ }
+ }
+
+ /// uses calloc to create the IVec, it's unsafe because
+ /// zeroed memory may not be a valid default value
+ pub unsafe fn zero(capacity: usize) -> Self {
+ Self {
+ ptr: calloc(capacity, size_of::<T>()) as *mut T,
+ length: capacity,
+ capacity,
+ element_size: size_of::<T>(),
+ }
+ }
+
+ pub fn with_capacity(capacity: usize) -> Self {
+ let mut vec = Self::new();
+ vec._set_capacity(capacity);
+ vec
+ }
+
+ pub fn with_capacity_and_default(capacity: usize, default_value: T) -> Self
+ where
+ T: Copy,
+ {
+ let mut vec = Self::new();
+ vec._set_capacity(capacity);
+ vec._buffer_mut().fill(default_value);
+ vec
+ }
+
+ pub unsafe fn from_raw_mut<'a>(raw: *mut Self) -> &'a mut Self {
+ let vec = raw.as_mut().expect("null pointer");
+ #[cfg(debug_assertions)]
+ vec.test_invariants();
+ vec
+ }
+
+ pub unsafe fn from_raw<'a>(raw: *const Self) -> &'a Self {
+ let vec = &*raw.as_ref().expect("null pointer");
+ #[cfg(debug_assertions)]
+ vec.test_invariants();
+ vec
+ }
+}
+
+/*
+ * private methods
+ */
+impl<T> IVec<T> {
+ pub fn test_invariants(&self) {
+ if !self.ptr.is_null() && (self.ptr as usize) % align_of::<T>() != 0 {
+ panic!(
+ "misaligned pointer: expected {:x}, got {:x}",
+ align_of::<T>(),
+ self.ptr as usize
+ );
+ }
+ if self.ptr.is_null() && (self.length > 0 || self.capacity > 0) {
+ panic!("ptr is null, but length or capacity is > 0");
+ }
+ if !self.ptr.is_null() && self.capacity == 0 {
+ panic!("ptr ISN'T null, but capacity == 0");
+ }
+ if self.element_size != size_of::<T>() {
+ panic!(
+ "incorrect element size, should be: {}, but was: {}",
+ size_of::<T>(),
+ self.element_size
+ );
+ }
+ if self.length > self.capacity {
+ panic!("length: {} > capacity: {}", self.length, self.capacity);
+ }
+ if self.capacity > usize::MAX / size_of::<T>() {
+ panic!(
+ "Capacity {} is too large, potential overflow detected",
+ self.capacity
+ );
+ }
+ }
+
+ fn _set_capacity(&mut self, new_capacity: usize) {
+ unsafe {
+ if new_capacity == self.capacity {
+ return;
+ }
+ if new_capacity < self.length {
+ self.truncate(new_capacity);
+ }
+ if new_capacity == 0 {
+ free(self.ptr as *mut c_void);
+ self.ptr = std::ptr::null_mut();
+ self.length = 0;
+ self.capacity = 0;
+ // DO NOT MODIFY element_size!!!
+ } else {
+ let t = realloc(self.ptr as *mut c_void, new_capacity * size_of::<T>());
+ if t.is_null() {
+ panic!("out of memory");
+ }
+ self.ptr = t as *mut T;
+ }
+ self.capacity = new_capacity;
+ }
+ }
+
+ fn _resize(&mut self, new_length: usize, default_value: T, exact: bool)
+ where
+ T: Copy,
+ {
+ if exact {
+ self._set_capacity(new_length);
+ } else if new_length > self.capacity {
+ self.reserve(new_length - self.capacity);
+ } else {
+ /* capacity does not need to be changed */
+ }
+
+ /* IVec grows */
+ if new_length > self.length {
+ let range = self.length..new_length;
+ self._buffer_mut()[range].fill(default_value);
+ self.length = new_length;
+ }
+
+ /* IVec shrinks */
+ if new_length < self.length {
+ self.truncate(new_length);
+ }
+ }
+
+ fn _buffer_mut(&mut self) -> &mut [T] {
+ if self.ptr.is_null() {
+ &mut []
+ } else {
+ unsafe { std::slice::from_raw_parts_mut(self.ptr, self.capacity) }
+ }
+ }
+
+ fn _buffer(&self) -> &[T] {
+ if self.ptr.is_null() {
+ &[]
+ } else {
+ unsafe { std::slice::from_raw_parts(self.ptr, self.capacity) }
+ }
+ }
+}
+
+/*
+ * methods
+ */
+impl<T> IVec<T> {
+ pub fn len(&self) -> usize {
+ self.length
+ }
+
+ pub unsafe fn set_len(&mut self, new_length: usize) {
+ self.length = new_length;
+ }
+
+ pub fn capacity(&self) -> usize {
+ self.capacity
+ }
+
+ pub fn reserve_exact(&mut self, additional: usize) {
+ self._set_capacity(self.capacity + additional);
+ }
+
+ pub fn reserve(&mut self, additional: usize) {
+ let growby = std::cmp::max(128, self.capacity);
+ self.reserve_exact(std::cmp::max(additional, growby));
+ }
+
+ pub fn shrink_to_fit(&mut self) {
+ self._set_capacity(self.length);
+ }
+
+ pub fn resize(&mut self, new_length: usize, default_value: T)
+ where
+ T: Copy,
+ {
+ self._resize(new_length, default_value, false);
+ }
+
+ pub fn resize_exact(&mut self, new_length: usize, default_value: T)
+ where
+ T: Copy,
+ {
+ self._resize(new_length, default_value, true);
+ }
+
+ pub fn truncate(&mut self, new_length: usize) {
+ if new_length >= self.length {
+ return;
+ }
+
+ if std::mem::needs_drop::<T>() {
+ let range = new_length..self.length;
+ for v in &mut self.as_mut_slice()[range] {
+ unsafe {
+ std::ptr::drop_in_place(v);
+ }
+ }
+ }
+
+ self.length = new_length;
+ }
+
+ pub fn insert(&mut self, index: usize, value: T) {
+ if self.length == self.capacity {
+ self.reserve(1);
+ }
+
+ unsafe {
+ let src = &self._buffer()[index] as *const T;
+ let dst = src.add(1) as *mut T;
+ let len = self.length - index;
+ std::ptr::copy(src, dst, len);
+ std::ptr::write(self.ptr.add(index), value);
+ }
+ }
+
+ pub fn push(&mut self, value: T) {
+ if self.length == self.capacity {
+ self.reserve(1);
+ }
+
+ let i = self.length;
+ unsafe {
+ std::ptr::write(self.ptr.add(i), value);
+ }
+ self.length += 1;
+ }
+
+ pub fn extend_from_slice(&mut self, slice: &[T])
+ where
+ T: Clone,
+ {
+ for v in slice {
+ self.push(v.clone());
+ }
+ }
+
+ pub fn clear(&mut self) {
+ self.length = 0;
+ }
+
+ pub fn as_ptr(&self) -> *const T {
+ self.ptr
+ }
+
+ pub fn as_mut_ptr(&self) -> *mut T {
+ self.ptr
+ }
+
+ pub fn as_slice(&self) -> &[T] {
+ &self._buffer()[0..self.length]
+ }
+
+ pub fn as_mut_slice(&mut self) -> &mut [T] {
+ let range = 0..self.length;
+ &mut self._buffer_mut()[range]
+ }
+}
+
+impl<T> Extend<T> for IVec<T> {
+ fn extend<IT: IntoIterator<Item = T>>(&mut self, iter: IT) {
+ for v in iter {
+ self.push(v);
+ }
+ }
+}
+
+impl<T> Index<usize> for IVec<T> {
+ type Output = T;
+
+ fn index(&self, index: usize) -> &Self::Output {
+ &self.as_slice()[index]
+ }
+}
+
+impl<T> IndexMut<usize> for IVec<T> {
+ fn index_mut(&mut self, index: usize) -> &mut Self::Output {
+ &mut self.as_mut_slice()[index]
+ }
+}
+
+impl<T: Debug> Debug for IVec<T> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ writeln!(
+ f,
+ "ptr: {}, capacity: {}, len: {}, element_size: {}, content: {:?}",
+ self.ptr as usize,
+ self.capacity,
+ self.length,
+ self.element_size,
+ self.as_slice()
+ )
+ }
+}
+
+impl std::fmt::Write for IVec<u8> {
+ fn write_str(&mut self, s: &str) -> std::fmt::Result {
+ Ok(self.extend_from_slice(s.as_bytes()))
+ }
+}
+
+impl std::io::Write for IVec<u8> {
+ fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+ self.extend_from_slice(buf);
+ Ok(buf.len())
+ }
+
+ fn flush(&mut self) -> std::io::Result<()> {
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::ivec::IVec;
+ use std::cell::RefCell;
+ use std::mem::size_of;
+ use std::ops::{Deref, DerefMut};
+ use std::panic;
+ use std::rc::Rc;
+
+ struct DropTest {
+ value: Rc<RefCell<u64>>,
+ }
+
+ impl DropTest {
+ fn new(value: Rc<RefCell<u64>>) -> Self {
+ Self { value }
+ }
+ }
+
+ impl Drop for DropTest {
+ fn drop(&mut self) {
+ *self.value.borrow_mut() -= 1;
+ }
+ }
+
+ #[test]
+ fn test_drop_elements() {
+ let counter = Rc::new(RefCell::new(0u64));
+ let size = 5;
+
+ /* drop whole IVec */
+ let mut vec = IVec::new();
+ for _ in 0..size {
+ let tmp = DropTest::new(counter.clone());
+ *tmp.value.borrow_mut() += 1;
+ vec.push(tmp);
+ }
+ let cur = *counter.borrow();
+ assert_eq!(size, cur);
+ drop(vec);
+
+ let cur = *counter.borrow();
+ assert_eq!(0u64, cur);
+
+ /* drop some elements */
+ let mut vec = IVec::new();
+ for i in 0..size {
+ let tmp = DropTest::new(counter.clone());
+ *tmp.value.borrow_mut() += 1;
+ vec.push(tmp);
+ }
+ let cur = *counter.borrow().deref();
+ assert_eq!(size, cur);
+
+ let expected = 2u64;
+ vec.truncate(expected as usize);
+
+ let cur = *counter.borrow();
+ assert_eq!(expected, cur);
+ drop(vec);
+ }
+
+ #[test]
+ fn test_panic_on_out_of_bounds() {
+ type TestType = i16;
+ let result = panic::catch_unwind(|| {
+ let mut v = IVec::<TestType>::with_capacity(1_000_000);
+ v[0] = 55;
+ });
+
+ match result {
+ Ok(_) => assert!(false, "index was out of bounds, but no panic was triggered"),
+ Err(_) => assert!(true),
+ }
+ }
+
+ #[test]
+ fn test_push_clear_resize_then_shrink_to_fit() {
+ let mut vec = IVec::<u64>::new();
+ let mut monotonic = 1;
+
+ vec.reserve_exact(1);
+ assert_eq!(1, vec.capacity);
+
+ // test push
+ for _ in 0..10 {
+ vec.push(monotonic);
+ assert_eq!(monotonic as usize, vec.length);
+ assert_eq!(monotonic, vec[(monotonic - 1) as usize]);
+ assert!(vec.capacity >= vec.length);
+ monotonic += 1;
+ }
+
+ // test clear
+ let expected = vec.capacity;
+ vec.clear();
+ assert_eq!(0, vec.length);
+ assert_eq!(expected, vec.capacity);
+
+ // test resize
+ let expected = vec.capacity + 10;
+ let default_value = 19;
+ vec.resize(expected, default_value);
+ assert_eq!(expected, vec.length);
+ assert!(vec.capacity >= expected);
+ for i in 0..vec.length {
+ assert_eq!(default_value, vec[i]);
+ }
+
+ vec.reserve(10);
+ assert!(vec.capacity > vec.length);
+ let length_before = vec.length;
+ vec.shrink_to_fit();
+ assert_eq!(length_before, vec.length);
+ assert_eq!(vec.length, vec.capacity);
+ }
+
+ #[test]
+ fn test_struct_size() {
+ let vec = IVec::<i16>::new();
+
+ assert_eq!(2, vec.element_size);
+ assert_eq!(size_of::<usize>() * 4, size_of::<IVec<i16>>());
+
+ drop(vec);
+
+ let vec = IVec::<u128>::new();
+ assert_eq!(16, vec.element_size);
+ assert_eq!(size_of::<usize>() * 4, size_of::<IVec<u128>>());
+ }
+}
diff --git a/rust/interop/src/lib.rs b/rust/interop/src/lib.rs
index e69de29bb2..4850f66e5b 100644
--- a/rust/interop/src/lib.rs
+++ b/rust/interop/src/lib.rs
@@ -0,0 +1,10 @@
+pub mod ivec;
+
+use std::ffi::c_void;
+
+extern "C" {
+ pub fn malloc(size: usize) -> *mut c_void;
+ pub fn calloc(nmemb: usize, size: usize) -> *mut c_void;
+ pub fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void;
+ pub fn free(ptr: *mut c_void);
+}
--
gitgitgadget
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 07/15] xdiff/xprepare: remove superfluous forward declarations
2025-08-29 19:42 [PATCH 00/15] Introduce rust: In xdiff Ezekiel Newren via GitGitGadget
` (5 preceding siblings ...)
2025-08-29 19:42 ` [PATCH 06/15] ivec: create a vector type that is interoperable between C and Rust Ezekiel Newren via GitGitGadget
@ 2025-08-29 19:42 ` Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 08/15] xdiff: delete unnecessary fields from xrecord_t and xdfile_t Ezekiel Newren via GitGitGadget
` (7 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Ezekiel Newren via GitGitGadget @ 2025-08-29 19:42 UTC (permalink / raw)
To: git; +Cc: Ezekiel Newren, Ezekiel Newren
From: Ezekiel Newren <ezekielnewren@gmail.com>
Move xdl_prepare_env() later in the file to avoid the need
for forward declarations.
Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
---
xdiff/xprepare.c | 116 ++++++++++++++++++++---------------------------
1 file changed, 50 insertions(+), 66 deletions(-)
diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c
index e1d4017b2d..a45c5ee208 100644
--- a/xdiff/xprepare.c
+++ b/xdiff/xprepare.c
@@ -53,21 +53,6 @@ typedef struct s_xdlclassifier {
-static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags);
-static void xdl_free_classifier(xdlclassifier_t *cf);
-static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
- unsigned int hbits, xrecord_t *rec);
-static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
- xdlclassifier_t *cf, xdfile_t *xdf);
-static void xdl_free_ctx(xdfile_t *xdf);
-static int xdl_clean_mmatch(char const *dis, long i, long s, long e);
-static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
-static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2);
-static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
-
-
-
-
static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {
cf->flags = flags;
@@ -242,57 +227,6 @@ static void xdl_free_ctx(xdfile_t *xdf) {
}
-int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
- xdfenv_t *xe) {
- long enl1, enl2, sample;
- xdlclassifier_t cf;
-
- memset(&cf, 0, sizeof(cf));
-
- /*
- * For histogram diff, we can afford a smaller sample size and
- * thus a poorer estimate of the number of lines, as the hash
- * table (rhash) won't be filled up/grown. The number of lines
- * (nrecs) will be updated correctly anyway by
- * xdl_prepare_ctx().
- */
- sample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF
- ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1);
-
- enl1 = xdl_guess_lines(mf1, sample) + 1;
- enl2 = xdl_guess_lines(mf2, sample) + 1;
-
- if (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)
- return -1;
-
- if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
-
- xdl_free_classifier(&cf);
- return -1;
- }
- if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
-
- xdl_free_ctx(&xe->xdf1);
- xdl_free_classifier(&cf);
- return -1;
- }
-
- if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
- (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&
- xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {
-
- xdl_free_ctx(&xe->xdf2);
- xdl_free_ctx(&xe->xdf1);
- xdl_free_classifier(&cf);
- return -1;
- }
-
- xdl_free_classifier(&cf);
-
- return 0;
-}
-
-
void xdl_free_env(xdfenv_t *xe) {
xdl_free_ctx(&xe->xdf2);
@@ -460,3 +394,53 @@ static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2
return 0;
}
+
+int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdfenv_t *xe) {
+ long enl1, enl2, sample;
+ xdlclassifier_t cf;
+
+ memset(&cf, 0, sizeof(cf));
+
+ /*
+ * For histogram diff, we can afford a smaller sample size and
+ * thus a poorer estimate of the number of lines, as the hash
+ * table (rhash) won't be filled up/grown. The number of lines
+ * (nrecs) will be updated correctly anyway by
+ * xdl_prepare_ctx().
+ */
+ sample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF
+ ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1);
+
+ enl1 = xdl_guess_lines(mf1, sample) + 1;
+ enl2 = xdl_guess_lines(mf2, sample) + 1;
+
+ if (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)
+ return -1;
+
+ if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
+
+ xdl_free_classifier(&cf);
+ return -1;
+ }
+ if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
+
+ xdl_free_ctx(&xe->xdf1);
+ xdl_free_classifier(&cf);
+ return -1;
+ }
+
+ if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
+ (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&
+ xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {
+
+ xdl_free_ctx(&xe->xdf2);
+ xdl_free_ctx(&xe->xdf1);
+ xdl_free_classifier(&cf);
+ return -1;
+ }
+
+ xdl_free_classifier(&cf);
+
+ return 0;
+}
--
gitgitgadget
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 08/15] xdiff: delete unnecessary fields from xrecord_t and xdfile_t
2025-08-29 19:42 [PATCH 00/15] Introduce rust: In xdiff Ezekiel Newren via GitGitGadget
` (6 preceding siblings ...)
2025-08-29 19:42 ` [PATCH 07/15] xdiff/xprepare: remove superfluous forward declarations Ezekiel Newren via GitGitGadget
@ 2025-08-29 19:42 ` Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 09/15] xdiff: make fields of xrecord_t Rust friendly Ezekiel Newren via GitGitGadget
` (6 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Ezekiel Newren via GitGitGadget @ 2025-08-29 19:42 UTC (permalink / raw)
To: git; +Cc: Ezekiel Newren, Ezekiel Newren
From: Ezekiel Newren <ezekielnewren@gmail.com>
xrecord_t.next, xdfile_t.hbits, xdfile_t.rhash are initialized,
but never used for anything by the code. Remove them.
Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
---
xdiff/xprepare.c | 24 +++---------------------
xdiff/xtypes.h | 3 ---
2 files changed, 3 insertions(+), 24 deletions(-)
diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c
index a45c5ee208..ad356281f9 100644
--- a/xdiff/xprepare.c
+++ b/xdiff/xprepare.c
@@ -91,8 +91,7 @@ static void xdl_free_classifier(xdlclassifier_t *cf) {
}
-static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
- unsigned int hbits, xrecord_t *rec) {
+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t *rec) {
long hi;
char const *line;
xdlclass_t *rcrec;
@@ -126,23 +125,17 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
rec->ha = (unsigned long) rcrec->idx;
- hi = (long) XDL_HASHLONG(rec->ha, hbits);
- rec->next = rhash[hi];
- rhash[hi] = rec;
-
return 0;
}
static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
xdlclassifier_t *cf, xdfile_t *xdf) {
- unsigned int hbits;
- long nrec, hsize, bsize;
+ long nrec, bsize;
unsigned long hav;
char const *blk, *cur, *top, *prev;
xrecord_t *crec;
xrecord_t **recs;
- xrecord_t **rhash;
unsigned long *ha;
char *rchg;
long *rindex;
@@ -150,7 +143,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
ha = NULL;
rindex = NULL;
rchg = NULL;
- rhash = NULL;
recs = NULL;
if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
@@ -158,11 +150,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
if (!XDL_ALLOC_ARRAY(recs, narec))
goto abort;
- hbits = xdl_hashbits((unsigned int) narec);
- hsize = 1 << hbits;
- if (!XDL_CALLOC_ARRAY(rhash, hsize))
- goto abort;
-
nrec = 0;
if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
for (top = blk + bsize; cur < top; ) {
@@ -176,7 +163,7 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
crec->size = (long) (cur - prev);
crec->ha = hav;
recs[nrec++] = crec;
- if (xdl_classify_record(pass, cf, rhash, hbits, crec) < 0)
+ if (xdl_classify_record(pass, cf, crec) < 0)
goto abort;
}
}
@@ -194,8 +181,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
xdf->nrec = nrec;
xdf->recs = recs;
- xdf->hbits = hbits;
- xdf->rhash = rhash;
xdf->rchg = rchg + 1;
xdf->rindex = rindex;
xdf->nreff = 0;
@@ -209,7 +194,6 @@ abort:
xdl_free(ha);
xdl_free(rindex);
xdl_free(rchg);
- xdl_free(rhash);
xdl_free(recs);
xdl_cha_free(&xdf->rcha);
return -1;
@@ -217,8 +201,6 @@ abort:
static void xdl_free_ctx(xdfile_t *xdf) {
-
- xdl_free(xdf->rhash);
xdl_free(xdf->rindex);
xdl_free(xdf->rchg - 1);
xdl_free(xdf->ha);
diff --git a/xdiff/xtypes.h b/xdiff/xtypes.h
index 8442bd436e..8b8467360e 100644
--- a/xdiff/xtypes.h
+++ b/xdiff/xtypes.h
@@ -39,7 +39,6 @@ typedef struct s_chastore {
} chastore_t;
typedef struct s_xrecord {
- struct s_xrecord *next;
char const *ptr;
long size;
unsigned long ha;
@@ -48,8 +47,6 @@ typedef struct s_xrecord {
typedef struct s_xdfile {
chastore_t rcha;
long nrec;
- unsigned int hbits;
- xrecord_t **rhash;
long dstart, dend;
xrecord_t **recs;
char *rchg;
--
gitgitgadget
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 09/15] xdiff: make fields of xrecord_t Rust friendly
2025-08-29 19:42 [PATCH 00/15] Introduce rust: In xdiff Ezekiel Newren via GitGitGadget
` (7 preceding siblings ...)
2025-08-29 19:42 ` [PATCH 08/15] xdiff: delete unnecessary fields from xrecord_t and xdfile_t Ezekiel Newren via GitGitGadget
@ 2025-08-29 19:42 ` Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 10/15] xdiff: use one definition for freeing xdfile_t Ezekiel Newren via GitGitGadget
` (5 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Ezekiel Newren via GitGitGadget @ 2025-08-29 19:42 UTC (permalink / raw)
To: git; +Cc: Ezekiel Newren, Ezekiel Newren
From: Ezekiel Newren <ezekielnewren@gmail.com>
A few commits ago, we added definitions for Rust primitive types,
to facilitate interoperability between C and Rust. Switch a
few variables to use these types. Which, for now, will
require adding some casts.
Also change xdlclass_t::ha to be u64 to match xrecord_t::ha, as
pointed out by Johannes.
Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
---
xdiff/xdiffi.c | 8 ++++----
xdiff/xemit.c | 2 +-
xdiff/xmerge.c | 14 +++++++-------
xdiff/xpatience.c | 2 +-
xdiff/xprepare.c | 8 ++++----
xdiff/xtypes.h | 6 +++---
xdiff/xutils.c | 4 ++--
7 files changed, 22 insertions(+), 22 deletions(-)
diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c
index 5a96e36dfb..3b364c61f6 100644
--- a/xdiff/xdiffi.c
+++ b/xdiff/xdiffi.c
@@ -418,7 +418,7 @@ static int get_indent(xrecord_t *rec)
long i;
int ret = 0;
- for (i = 0; i < rec->size; i++) {
+ for (i = 0; i < (long) rec->size; i++) {
char c = rec->ptr[i];
if (!XDL_ISSPACE(c))
@@ -1005,11 +1005,11 @@ static void xdl_mark_ignorable_lines(xdchange_t *xscr, xdfenv_t *xe, long flags)
rec = &xe->xdf1.recs[xch->i1];
for (i = 0; i < xch->chg1 && ignore; i++)
- ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
+ ignore = xdl_blankline((const char*) rec[i]->ptr, rec[i]->size, flags);
rec = &xe->xdf2.recs[xch->i2];
for (i = 0; i < xch->chg2 && ignore; i++)
- ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
+ ignore = xdl_blankline((const char*)rec[i]->ptr, rec[i]->size, flags);
xch->ignore = ignore;
}
@@ -1020,7 +1020,7 @@ static int record_matches_regex(xrecord_t *rec, xpparam_t const *xpp) {
size_t i;
for (i = 0; i < xpp->ignore_regex_nr; i++)
- if (!regexec_buf(xpp->ignore_regex[i], rec->ptr, rec->size, 1,
+ if (!regexec_buf(xpp->ignore_regex[i], (const char*) rec->ptr, rec->size, 1,
®match, 0))
return 1;
diff --git a/xdiff/xemit.c b/xdiff/xemit.c
index 1d40c9cb40..bbf7b7f8c8 100644
--- a/xdiff/xemit.c
+++ b/xdiff/xemit.c
@@ -24,7 +24,7 @@
static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) {
- *rec = xdf->recs[ri]->ptr;
+ *rec = (char const*) xdf->recs[ri]->ptr;
return xdf->recs[ri]->size;
}
diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c
index af40c88a5b..6fa6ea61a2 100644
--- a/xdiff/xmerge.c
+++ b/xdiff/xmerge.c
@@ -101,8 +101,8 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,
xrecord_t **rec2 = xe2->xdf2.recs + i2;
for (i = 0; i < line_count; i++) {
- int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size,
- rec2[i]->ptr, rec2[i]->size, flags);
+ int result = xdl_recmatch((const char*) rec1[i]->ptr, rec1[i]->size,
+ (const char*) rec2[i]->ptr, rec2[i]->size, flags);
if (!result)
return -1;
}
@@ -324,8 +324,8 @@ static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,
static int recmatch(xrecord_t *rec1, xrecord_t *rec2, unsigned long flags)
{
- return xdl_recmatch(rec1->ptr, rec1->size,
- rec2->ptr, rec2->size, flags);
+ return xdl_recmatch((char const*) rec1->ptr, rec1->size,
+ (char const*) rec2->ptr, rec2->size, flags);
}
/*
@@ -383,10 +383,10 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,
*/
t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr;
t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr
- + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr;
+ + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - (u8 const*) t1.ptr;
t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr;
t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr
- + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr;
+ + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - (u8 const*) t2.ptr;
if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0)
return -1;
if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 ||
@@ -440,7 +440,7 @@ static int line_contains_alnum(const char *ptr, long size)
static int lines_contain_alnum(xdfenv_t *xe, int i, int chg)
{
for (; chg; chg--, i++)
- if (line_contains_alnum(xe->xdf2.recs[i]->ptr,
+ if (line_contains_alnum((char const*) xe->xdf2.recs[i]->ptr,
xe->xdf2.recs[i]->size))
return 1;
return 0;
diff --git a/xdiff/xpatience.c b/xdiff/xpatience.c
index 77dc411d19..986a3a3f74 100644
--- a/xdiff/xpatience.c
+++ b/xdiff/xpatience.c
@@ -121,7 +121,7 @@ static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
return;
map->entries[index].line1 = line;
map->entries[index].hash = record->ha;
- map->entries[index].anchor = is_anchor(xpp, map->env->xdf1.recs[line - 1]->ptr);
+ map->entries[index].anchor = is_anchor(xpp, (const char*) map->env->xdf1.recs[line - 1]->ptr);
if (!map->first)
map->first = map->entries + index;
if (map->last) {
diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c
index ad356281f9..00cdf7d8a0 100644
--- a/xdiff/xprepare.c
+++ b/xdiff/xprepare.c
@@ -32,7 +32,7 @@
typedef struct s_xdlclass {
struct s_xdlclass *next;
- unsigned long ha;
+ u64 ha;
char const *line;
long size;
long idx;
@@ -96,12 +96,12 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
char const *line;
xdlclass_t *rcrec;
- line = rec->ptr;
+ line = (char const*) rec->ptr;
hi = (long) XDL_HASHLONG(rec->ha, cf->hbits);
for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next)
if (rcrec->ha == rec->ha &&
xdl_recmatch(rcrec->line, rcrec->size,
- rec->ptr, rec->size, cf->flags))
+ (const char*) rec->ptr, rec->size, cf->flags))
break;
if (!rcrec) {
@@ -159,7 +159,7 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
goto abort;
if (!(crec = xdl_cha_alloc(&xdf->rcha)))
goto abort;
- crec->ptr = prev;
+ crec->ptr = (u8 const*) prev;
crec->size = (long) (cur - prev);
crec->ha = hav;
recs[nrec++] = crec;
diff --git a/xdiff/xtypes.h b/xdiff/xtypes.h
index 8b8467360e..6e5f67ebf3 100644
--- a/xdiff/xtypes.h
+++ b/xdiff/xtypes.h
@@ -39,9 +39,9 @@ typedef struct s_chastore {
} chastore_t;
typedef struct s_xrecord {
- char const *ptr;
- long size;
- unsigned long ha;
+ u8 const* ptr;
+ usize size;
+ u64 ha;
} xrecord_t;
typedef struct s_xdfile {
diff --git a/xdiff/xutils.c b/xdiff/xutils.c
index 444a108f87..10e4f20b7c 100644
--- a/xdiff/xutils.c
+++ b/xdiff/xutils.c
@@ -418,10 +418,10 @@ int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr;
subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr +
- diff_env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr;
+ diff_env->xdf1.recs[line1 + count1 - 2]->size - (u8 const*) subfile1.ptr;
subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr;
subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr +
- diff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr;
+ diff_env->xdf2.recs[line2 + count2 - 2]->size - (u8 const*) subfile2.ptr;
if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0)
return -1;
--
gitgitgadget
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 10/15] xdiff: use one definition for freeing xdfile_t
2025-08-29 19:42 [PATCH 00/15] Introduce rust: In xdiff Ezekiel Newren via GitGitGadget
` (8 preceding siblings ...)
2025-08-29 19:42 ` [PATCH 09/15] xdiff: make fields of xrecord_t Rust friendly Ezekiel Newren via GitGitGadget
@ 2025-08-29 19:42 ` Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 11/15] xdiff: replace chastore with an ivec in xdfile_t Ezekiel Newren via GitGitGadget
` (4 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Ezekiel Newren via GitGitGadget @ 2025-08-29 19:42 UTC (permalink / raw)
To: git; +Cc: Ezekiel Newren, Ezekiel Newren
From: Ezekiel Newren <ezekielnewren@gmail.com>
Simplify xdl_prepare_ctx() by using xdl_free_ctx() instead of using
local variables with hand rolled memory management.
Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
---
xdiff/xprepare.c | 60 +++++++++++++++++++-----------------------------
1 file changed, 24 insertions(+), 36 deletions(-)
diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c
index 00cdf7d8a0..55e1cc3087 100644
--- a/xdiff/xprepare.c
+++ b/xdiff/xprepare.c
@@ -129,86 +129,74 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
}
+static void xdl_free_ctx(xdfile_t *xdf) {
+ xdl_free(xdf->rindex);
+ xdl_free(xdf->rchg - 1);
+ xdl_free(xdf->ha);
+ xdl_free(xdf->recs);
+ xdl_cha_free(&xdf->rcha);
+}
+
+
static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
xdlclassifier_t *cf, xdfile_t *xdf) {
- long nrec, bsize;
+ long bsize;
unsigned long hav;
char const *blk, *cur, *top, *prev;
xrecord_t *crec;
- xrecord_t **recs;
- unsigned long *ha;
- char *rchg;
- long *rindex;
- ha = NULL;
- rindex = NULL;
- rchg = NULL;
- recs = NULL;
+ xdf->ha = NULL;
+ xdf->rindex = NULL;
+ xdf->rchg = NULL;
+ xdf->recs = NULL;
+ xdf->nrec = 0;
if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
goto abort;
- if (!XDL_ALLOC_ARRAY(recs, narec))
+ if (!XDL_ALLOC_ARRAY(xdf->recs, narec))
goto abort;
- nrec = 0;
if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
for (top = blk + bsize; cur < top; ) {
prev = cur;
hav = xdl_hash_record(&cur, top, xpp->flags);
- if (XDL_ALLOC_GROW(recs, nrec + 1, narec))
+ if (XDL_ALLOC_GROW(xdf->recs, xdf->nrec + 1, narec))
goto abort;
if (!(crec = xdl_cha_alloc(&xdf->rcha)))
goto abort;
crec->ptr = (u8 const*) prev;
crec->size = (long) (cur - prev);
crec->ha = hav;
- recs[nrec++] = crec;
+ xdf->recs[xdf->nrec++] = crec;
if (xdl_classify_record(pass, cf, crec) < 0)
goto abort;
}
}
- if (!XDL_CALLOC_ARRAY(rchg, nrec + 2))
+ if (!XDL_CALLOC_ARRAY(xdf->rchg, xdf->nrec + 2))
goto abort;
if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
(XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) {
- if (!XDL_ALLOC_ARRAY(rindex, nrec + 1))
+ if (!XDL_ALLOC_ARRAY(xdf->rindex, xdf->nrec + 1))
goto abort;
- if (!XDL_ALLOC_ARRAY(ha, nrec + 1))
+ if (!XDL_ALLOC_ARRAY(xdf->ha, xdf->nrec + 1))
goto abort;
}
- xdf->nrec = nrec;
- xdf->recs = recs;
- xdf->rchg = rchg + 1;
- xdf->rindex = rindex;
+ xdf->rchg += 1;
xdf->nreff = 0;
- xdf->ha = ha;
xdf->dstart = 0;
- xdf->dend = nrec - 1;
+ xdf->dend = xdf->nrec - 1;
return 0;
abort:
- xdl_free(ha);
- xdl_free(rindex);
- xdl_free(rchg);
- xdl_free(recs);
- xdl_cha_free(&xdf->rcha);
+ xdl_free_ctx(xdf);
return -1;
}
-static void xdl_free_ctx(xdfile_t *xdf) {
- xdl_free(xdf->rindex);
- xdl_free(xdf->rchg - 1);
- xdl_free(xdf->ha);
- xdl_free(xdf->recs);
- xdl_cha_free(&xdf->rcha);
-}
-
-
void xdl_free_env(xdfenv_t *xe) {
xdl_free_ctx(&xe->xdf2);
--
gitgitgadget
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 11/15] xdiff: replace chastore with an ivec in xdfile_t
2025-08-29 19:42 [PATCH 00/15] Introduce rust: In xdiff Ezekiel Newren via GitGitGadget
` (9 preceding siblings ...)
2025-08-29 19:42 ` [PATCH 10/15] xdiff: use one definition for freeing xdfile_t Ezekiel Newren via GitGitGadget
@ 2025-08-29 19:42 ` Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 12/15] xdiff: delete nrec field from xdfile_t Ezekiel Newren via GitGitGadget
` (3 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Ezekiel Newren via GitGitGadget @ 2025-08-29 19:42 UTC (permalink / raw)
To: git; +Cc: Ezekiel Newren, Ezekiel Newren
From: Ezekiel Newren <ezekielnewren@gmail.com>
xdfile_t currently uses a chastore which functions as a memory pool and
a vector which maps to the allocations created by the chastore. It seems
like xrecord_t used to be a linked list until the recs and nrec fields
were added. I think that xrecord_t.next was meant to be removed, but
was overlooked. This dual data structure setup make the code somewhat
confusing.
Additionally the C type chastore_t isn't FFI friendly. While it could
be implemented in Rust, since the data structure is confusing anyway,
replace it with an ivec whose purpose is to be interoperable. This
makes the fields nrec and recs in xdfile_t redundant, which will be
removed in the next 2 commits.
Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
---
xdiff/xprepare.c | 34 +++++++++++++++++-----------------
xdiff/xtypes.h | 6 ++++--
2 files changed, 21 insertions(+), 19 deletions(-)
diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c
index 55e1cc3087..3b33186c15 100644
--- a/xdiff/xprepare.c
+++ b/xdiff/xprepare.c
@@ -130,11 +130,11 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
static void xdl_free_ctx(xdfile_t *xdf) {
+ ivec_free(&xdf->record);
xdl_free(xdf->rindex);
xdl_free(xdf->rchg - 1);
xdl_free(xdf->ha);
xdl_free(xdf->recs);
- xdl_cha_free(&xdf->rcha);
}
@@ -143,35 +143,35 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
long bsize;
unsigned long hav;
char const *blk, *cur, *top, *prev;
- xrecord_t *crec;
xdf->ha = NULL;
xdf->rindex = NULL;
xdf->rchg = NULL;
xdf->recs = NULL;
xdf->nrec = 0;
-
- if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
- goto abort;
- if (!XDL_ALLOC_ARRAY(xdf->recs, narec))
- goto abort;
+ IVEC_INIT(xdf->record);
if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
for (top = blk + bsize; cur < top; ) {
+ xrecord_t crec;
prev = cur;
hav = xdl_hash_record(&cur, top, xpp->flags);
- if (XDL_ALLOC_GROW(xdf->recs, xdf->nrec + 1, narec))
- goto abort;
- if (!(crec = xdl_cha_alloc(&xdf->rcha)))
- goto abort;
- crec->ptr = (u8 const*) prev;
- crec->size = (long) (cur - prev);
- crec->ha = hav;
- xdf->recs[xdf->nrec++] = crec;
- if (xdl_classify_record(pass, cf, crec) < 0)
- goto abort;
+ crec.ptr = (u8 const*) prev;
+ crec.size = cur - prev;
+ crec.ha = hav;
+ ivec_push(&xdf->record, &crec);
}
}
+ ivec_shrink_to_fit(&xdf->record);
+
+ xdf->nrec = (long) xdf->record.length;
+ if (!XDL_ALLOC_ARRAY(xdf->recs, xdf->record.length))
+ goto abort;
+ for (usize i = 0; i < xdf->record.length; i++) {
+ if (xdl_classify_record(pass, cf, &xdf->record.ptr[i]) < 0)
+ goto abort;
+ xdf->recs[i] = &xdf->record.ptr[i];
+ }
if (!XDL_CALLOC_ARRAY(xdf->rchg, xdf->nrec + 2))
goto abort;
diff --git a/xdiff/xtypes.h b/xdiff/xtypes.h
index 6e5f67ebf3..5028a70b26 100644
--- a/xdiff/xtypes.h
+++ b/xdiff/xtypes.h
@@ -23,7 +23,7 @@
#if !defined(XTYPES_H)
#define XTYPES_H
-
+#include "../interop/ivec.h"
typedef struct s_chanode {
struct s_chanode *next;
@@ -44,8 +44,10 @@ typedef struct s_xrecord {
u64 ha;
} xrecord_t;
+DEFINE_IVEC_TYPE(xrecord_t, xrecord);
+
typedef struct s_xdfile {
- chastore_t rcha;
+ struct ivec_xrecord record;
long nrec;
long dstart, dend;
xrecord_t **recs;
--
gitgitgadget
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 12/15] xdiff: delete nrec field from xdfile_t
2025-08-29 19:42 [PATCH 00/15] Introduce rust: In xdiff Ezekiel Newren via GitGitGadget
` (10 preceding siblings ...)
2025-08-29 19:42 ` [PATCH 11/15] xdiff: replace chastore with an ivec in xdfile_t Ezekiel Newren via GitGitGadget
@ 2025-08-29 19:42 ` Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 13/15] xdiff: delete recs " Ezekiel Newren via GitGitGadget
` (2 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Ezekiel Newren via GitGitGadget @ 2025-08-29 19:42 UTC (permalink / raw)
To: git; +Cc: Ezekiel Newren, Ezekiel Newren
From: Ezekiel Newren <ezekielnewren@gmail.com>
Because of the data structure cleanup in the previous commit, the nrec
field is no longer necessary. Use record.length in place of nrec.
This commit is best viewed with --color-words.
Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
---
xdiff/xdiffi.c | 10 +++++-----
xdiff/xemit.c | 20 ++++++++++----------
xdiff/xmerge.c | 10 +++++-----
xdiff/xpatience.c | 2 +-
xdiff/xprepare.c | 34 ++++++++++++++++------------------
xdiff/xtypes.h | 1 -
6 files changed, 37 insertions(+), 40 deletions(-)
diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c
index 3b364c61f6..bcab2d7ae5 100644
--- a/xdiff/xdiffi.c
+++ b/xdiff/xdiffi.c
@@ -496,7 +496,7 @@ static void measure_split(const xdfile_t *xdf, long split,
{
long i;
- if (split >= xdf->nrec) {
+ if (split >= (long) xdf->record.length) {
m->end_of_file = 1;
m->indent = -1;
} else {
@@ -519,7 +519,7 @@ static void measure_split(const xdfile_t *xdf, long split,
m->post_blank = 0;
m->post_indent = -1;
- for (i = split + 1; i < xdf->nrec; i++) {
+ for (i = split + 1; i < (long) xdf->record.length; i++) {
m->post_indent = get_indent(xdf->recs[i]);
if (m->post_indent != -1)
break;
@@ -730,7 +730,7 @@ static void group_init(xdfile_t *xdf, struct xdlgroup *g)
*/
static inline int group_next(xdfile_t *xdf, struct xdlgroup *g)
{
- if (g->end == xdf->nrec)
+ if (g->end == (long) xdf->record.length)
return -1;
g->start = g->end + 1;
@@ -763,7 +763,7 @@ static inline int group_previous(xdfile_t *xdf, struct xdlgroup *g)
*/
static int group_slide_down(xdfile_t *xdf, struct xdlgroup *g)
{
- if (g->end < xdf->nrec &&
+ if (g->end < (long) xdf->record.length &&
recs_match(xdf->recs[g->start], xdf->recs[g->end])) {
xdf->rchg[g->start++] = 0;
xdf->rchg[g->end++] = 1;
@@ -950,7 +950,7 @@ int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr) {
/*
* Trivial. Collects "groups" of changes and creates an edit script.
*/
- for (i1 = xe->xdf1.nrec, i2 = xe->xdf2.nrec; i1 >= 0 || i2 >= 0; i1--, i2--)
+ for (i1 = xe->xdf1.record.length, i2 = xe->xdf2.record.length; i1 >= 0 || i2 >= 0; i1--, i2--)
if (rchg1[i1 - 1] || rchg2[i2 - 1]) {
for (l1 = i1; rchg1[i1 - 1]; i1--);
for (l2 = i2; rchg2[i2 - 1]; i2--);
diff --git a/xdiff/xemit.c b/xdiff/xemit.c
index bbf7b7f8c8..11c2823eca 100644
--- a/xdiff/xemit.c
+++ b/xdiff/xemit.c
@@ -147,7 +147,7 @@ static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg,
buf = func_line ? func_line->buf : dummy;
size = func_line ? sizeof(func_line->buf) : sizeof(dummy);
- for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) {
+ for (l = start; l != limit && 0 <= l && l < (long) xe->xdf1.record.length; l += step) {
long len = match_func_rec(&xe->xdf1, xecfg, l, buf, size);
if (len >= 0) {
if (func_line)
@@ -191,14 +191,14 @@ pre_context_calculation:
long fs1, i1 = xch->i1;
/* Appended chunk? */
- if (i1 >= xe->xdf1.nrec) {
+ if (i1 >= (long) xe->xdf1.record.length) {
long i2 = xch->i2;
/*
* We don't need additional context if
* a whole function was added.
*/
- while (i2 < xe->xdf2.nrec) {
+ while (i2 < (long) xe->xdf2.record.length) {
if (is_func_rec(&xe->xdf2, xecfg, i2))
goto post_context_calculation;
i2++;
@@ -208,7 +208,7 @@ pre_context_calculation:
* Otherwise get more context from the
* pre-image.
*/
- i1 = xe->xdf1.nrec - 1;
+ i1 = xe->xdf1.record.length - 1;
}
fs1 = get_func_line(xe, xecfg, NULL, i1, -1);
@@ -240,8 +240,8 @@ pre_context_calculation:
post_context_calculation:
lctx = xecfg->ctxlen;
- lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1));
- lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2));
+ lctx = XDL_MIN(lctx, (long) xe->xdf1.record.length - (xche->i1 + xche->chg1));
+ lctx = XDL_MIN(lctx, (long) xe->xdf2.record.length - (xche->i2 + xche->chg2));
e1 = xche->i1 + xche->chg1 + lctx;
e2 = xche->i2 + xche->chg2 + lctx;
@@ -249,13 +249,13 @@ pre_context_calculation:
if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
long fe1 = get_func_line(xe, xecfg, NULL,
xche->i1 + xche->chg1,
- xe->xdf1.nrec);
+ xe->xdf1.record.length);
while (fe1 > 0 && is_empty_rec(&xe->xdf1, fe1 - 1))
fe1--;
if (fe1 < 0)
- fe1 = xe->xdf1.nrec;
+ fe1 = xe->xdf1.record.length;
if (fe1 > e1) {
- e2 = XDL_MIN(e2 + (fe1 - e1), xe->xdf2.nrec);
+ e2 = XDL_MIN(e2 + (fe1 - e1), (long) xe->xdf2.record.length);
e1 = fe1;
}
@@ -266,7 +266,7 @@ pre_context_calculation:
*/
if (xche->next) {
long l = XDL_MIN(xche->next->i1,
- xe->xdf1.nrec - 1);
+ (long) xe->xdf1.record.length - 1);
if (l - xecfg->ctxlen <= e1 ||
get_func_line(xe, xecfg, NULL, l, e1) < 0) {
xche = xche->next;
diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c
index 6fa6ea61a2..f48549605d 100644
--- a/xdiff/xmerge.c
+++ b/xdiff/xmerge.c
@@ -158,11 +158,11 @@ static int is_eol_crlf(xdfile_t *file, int i)
{
long size;
- if (i < file->nrec - 1)
+ if (i < (isize) file->record.length - 1)
/* All lines before the last *must* end in LF */
return (size = file->recs[i]->size) > 1 &&
file->recs[i]->ptr[size - 2] == '\r';
- if (!file->nrec)
+ if (!file->record.length)
/* Cannot determine eol style from empty file */
return -1;
if ((size = file->recs[i]->size) &&
@@ -317,7 +317,7 @@ static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,
continue;
i = m->i1 + m->chg1;
}
- size += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0, 0,
+ size += xdl_recs_copy(xe1, i, (int) xe1->xdf2.record.length - i, 0, 0,
dest ? dest + size : NULL);
return size;
}
@@ -622,7 +622,7 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1,
changes = c;
i0 = xscr1->i1;
i1 = xscr1->i2;
- i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec;
+ i2 = xscr1->i1 + xe2->xdf2.record.length - xe2->xdf1.record.length;
chg0 = xscr1->chg1;
chg1 = xscr1->chg2;
chg2 = xscr1->chg1;
@@ -637,7 +637,7 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1,
if (!changes)
changes = c;
i0 = xscr2->i1;
- i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec;
+ i1 = xscr2->i1 + xe1->xdf2.record.length - xe1->xdf1.record.length;
i2 = xscr2->i2;
chg0 = xscr2->chg1;
chg1 = xscr2->chg1;
diff --git a/xdiff/xpatience.c b/xdiff/xpatience.c
index 986a3a3f74..e1ce9a399f 100644
--- a/xdiff/xpatience.c
+++ b/xdiff/xpatience.c
@@ -370,5 +370,5 @@ static int patience_diff(xpparam_t const *xpp, xdfenv_t *env,
int xdl_do_patience_diff(xpparam_t const *xpp, xdfenv_t *env)
{
- return patience_diff(xpp, env, 1, env->xdf1.nrec, 1, env->xdf2.nrec);
+ return patience_diff(xpp, env, 1, env->xdf1.record.length, 1, env->xdf2.record.length);
}
diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c
index 3b33186c15..9b46523afe 100644
--- a/xdiff/xprepare.c
+++ b/xdiff/xprepare.c
@@ -138,7 +138,7 @@ static void xdl_free_ctx(xdfile_t *xdf) {
}
-static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
+static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, xpparam_t const *xpp,
xdlclassifier_t *cf, xdfile_t *xdf) {
long bsize;
unsigned long hav;
@@ -148,7 +148,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
xdf->rindex = NULL;
xdf->rchg = NULL;
xdf->recs = NULL;
- xdf->nrec = 0;
IVEC_INIT(xdf->record);
if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
@@ -164,7 +163,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
}
ivec_shrink_to_fit(&xdf->record);
- xdf->nrec = (long) xdf->record.length;
if (!XDL_ALLOC_ARRAY(xdf->recs, xdf->record.length))
goto abort;
for (usize i = 0; i < xdf->record.length; i++) {
@@ -173,21 +171,21 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
xdf->recs[i] = &xdf->record.ptr[i];
}
- if (!XDL_CALLOC_ARRAY(xdf->rchg, xdf->nrec + 2))
+ if (!XDL_CALLOC_ARRAY(xdf->rchg, xdf->record.length + 2))
goto abort;
if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
(XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) {
- if (!XDL_ALLOC_ARRAY(xdf->rindex, xdf->nrec + 1))
+ if (!XDL_ALLOC_ARRAY(xdf->rindex, xdf->record.length + 1))
goto abort;
- if (!XDL_ALLOC_ARRAY(xdf->ha, xdf->nrec + 1))
+ if (!XDL_ALLOC_ARRAY(xdf->ha, xdf->record.length + 1))
goto abort;
}
xdf->rchg += 1;
xdf->nreff = 0;
xdf->dstart = 0;
- xdf->dend = xdf->nrec - 1;
+ xdf->dend = xdf->record.length - 1;
return 0;
@@ -274,12 +272,12 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd
char *dis, *dis1, *dis2;
int need_min = !!(cf->flags & XDF_NEED_MINIMAL);
- if (!XDL_CALLOC_ARRAY(dis, xdf1->nrec + xdf2->nrec + 2))
+ if (!XDL_CALLOC_ARRAY(dis, xdf1->record.length + xdf2->record.length + 2))
return -1;
dis1 = dis;
- dis2 = dis1 + xdf1->nrec + 1;
+ dis2 = dis1 + xdf1->record.length + 1;
- if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT)
+ if ((mlim = xdl_bogosqrt(xdf1->record.length)) > XDL_MAX_EQLIMIT)
mlim = XDL_MAX_EQLIMIT;
for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) {
rcrec = cf->rcrecs[(*recs)->ha];
@@ -287,7 +285,7 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd
dis1[i] = (nm == 0) ? 0: (nm >= mlim && !need_min) ? 2: 1;
}
- if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT)
+ if ((mlim = xdl_bogosqrt(xdf2->record.length)) > XDL_MAX_EQLIMIT)
mlim = XDL_MAX_EQLIMIT;
for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {
rcrec = cf->rcrecs[(*recs)->ha];
@@ -334,21 +332,21 @@ static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {
recs1 = xdf1->recs;
recs2 = xdf2->recs;
- for (i = 0, lim = XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim;
+ for (i = 0, lim = XDL_MIN(xdf1->record.length, xdf2->record.length); i < lim;
i++, recs1++, recs2++)
if ((*recs1)->ha != (*recs2)->ha)
break;
xdf1->dstart = xdf2->dstart = i;
- recs1 = xdf1->recs + xdf1->nrec - 1;
- recs2 = xdf2->recs + xdf2->nrec - 1;
+ recs1 = xdf1->recs + xdf1->record.length - 1;
+ recs2 = xdf2->recs + xdf2->record.length - 1;
for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--)
if ((*recs1)->ha != (*recs2)->ha)
break;
- xdf1->dend = xdf1->nrec - i - 1;
- xdf2->dend = xdf2->nrec - i - 1;
+ xdf1->dend = xdf1->record.length - i - 1;
+ xdf2->dend = xdf2->record.length - i - 1;
return 0;
}
@@ -388,12 +386,12 @@ int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
if (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)
return -1;
- if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
+ if (xdl_prepare_ctx(1, mf1, xpp, &cf, &xe->xdf1) < 0) {
xdl_free_classifier(&cf);
return -1;
}
- if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
+ if (xdl_prepare_ctx(2, mf2, xpp, &cf, &xe->xdf2) < 0) {
xdl_free_ctx(&xe->xdf1);
xdl_free_classifier(&cf);
diff --git a/xdiff/xtypes.h b/xdiff/xtypes.h
index 5028a70b26..c322e62fbf 100644
--- a/xdiff/xtypes.h
+++ b/xdiff/xtypes.h
@@ -48,7 +48,6 @@ DEFINE_IVEC_TYPE(xrecord_t, xrecord);
typedef struct s_xdfile {
struct ivec_xrecord record;
- long nrec;
long dstart, dend;
xrecord_t **recs;
char *rchg;
--
gitgitgadget
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 13/15] xdiff: delete recs field from xdfile_t
2025-08-29 19:42 [PATCH 00/15] Introduce rust: In xdiff Ezekiel Newren via GitGitGadget
` (11 preceding siblings ...)
2025-08-29 19:42 ` [PATCH 12/15] xdiff: delete nrec field from xdfile_t Ezekiel Newren via GitGitGadget
@ 2025-08-29 19:42 ` Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 14/15] xdiff: make xdfile_t more rust friendly Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 15/15] xdiff: implement xdl_trim_ends() in Rust Ezekiel Newren via GitGitGadget
14 siblings, 0 replies; 20+ messages in thread
From: Ezekiel Newren via GitGitGadget @ 2025-08-29 19:42 UTC (permalink / raw)
To: git; +Cc: Ezekiel Newren, Ezekiel Newren
From: Ezekiel Newren <ezekielnewren@gmail.com>
Because of the change from chastore to ivec a few commits ago,
recs now points to record's elements in a 1:1 mapping.
Since both recs and record are vectors, this additional mapping is
superfluous. Remove recs.
This commit is best viewed with --color-words.
Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
---
xdiff/xdiffi.c | 30 ++++++++++++------------
xdiff/xemit.c | 4 ++--
xdiff/xhistogram.c | 2 +-
xdiff/xmerge.c | 58 +++++++++++++++++++++++-----------------------
xdiff/xpatience.c | 14 +++++------
xdiff/xprepare.c | 37 +++++++++++++----------------
xdiff/xtypes.h | 1 -
xdiff/xutils.c | 12 +++++-----
8 files changed, 76 insertions(+), 82 deletions(-)
diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c
index bcab2d7ae5..ebdb724322 100644
--- a/xdiff/xdiffi.c
+++ b/xdiff/xdiffi.c
@@ -501,13 +501,13 @@ static void measure_split(const xdfile_t *xdf, long split,
m->indent = -1;
} else {
m->end_of_file = 0;
- m->indent = get_indent(xdf->recs[split]);
+ m->indent = get_indent(&xdf->record.ptr[split]);
}
m->pre_blank = 0;
m->pre_indent = -1;
for (i = split - 1; i >= 0; i--) {
- m->pre_indent = get_indent(xdf->recs[i]);
+ m->pre_indent = get_indent(&xdf->record.ptr[i]);
if (m->pre_indent != -1)
break;
m->pre_blank += 1;
@@ -520,7 +520,7 @@ static void measure_split(const xdfile_t *xdf, long split,
m->post_blank = 0;
m->post_indent = -1;
for (i = split + 1; i < (long) xdf->record.length; i++) {
- m->post_indent = get_indent(xdf->recs[i]);
+ m->post_indent = get_indent(&xdf->record.ptr[i]);
if (m->post_indent != -1)
break;
m->post_blank += 1;
@@ -764,7 +764,7 @@ static inline int group_previous(xdfile_t *xdf, struct xdlgroup *g)
static int group_slide_down(xdfile_t *xdf, struct xdlgroup *g)
{
if (g->end < (long) xdf->record.length &&
- recs_match(xdf->recs[g->start], xdf->recs[g->end])) {
+ recs_match(&xdf->record.ptr[g->start], &xdf->record.ptr[g->end])) {
xdf->rchg[g->start++] = 0;
xdf->rchg[g->end++] = 1;
@@ -785,7 +785,7 @@ static int group_slide_down(xdfile_t *xdf, struct xdlgroup *g)
static int group_slide_up(xdfile_t *xdf, struct xdlgroup *g)
{
if (g->start > 0 &&
- recs_match(xdf->recs[g->start - 1], xdf->recs[g->end - 1])) {
+ recs_match(&xdf->record.ptr[g->start - 1], &xdf->record.ptr[g->end - 1])) {
xdf->rchg[--g->start] = 1;
xdf->rchg[--g->end] = 0;
@@ -1000,16 +1000,16 @@ static void xdl_mark_ignorable_lines(xdchange_t *xscr, xdfenv_t *xe, long flags)
for (xch = xscr; xch; xch = xch->next) {
int ignore = 1;
- xrecord_t **rec;
+ xrecord_t *rec;
long i;
- rec = &xe->xdf1.recs[xch->i1];
+ rec = &xe->xdf1.record.ptr[xch->i1];
for (i = 0; i < xch->chg1 && ignore; i++)
- ignore = xdl_blankline((const char*) rec[i]->ptr, rec[i]->size, flags);
+ ignore = xdl_blankline((const char*) rec[i].ptr, rec[i].size, flags);
- rec = &xe->xdf2.recs[xch->i2];
+ rec = &xe->xdf2.record.ptr[xch->i2];
for (i = 0; i < xch->chg2 && ignore; i++)
- ignore = xdl_blankline((const char*)rec[i]->ptr, rec[i]->size, flags);
+ ignore = xdl_blankline((const char*)rec[i].ptr, rec[i].size, flags);
xch->ignore = ignore;
}
@@ -1033,7 +1033,7 @@ static void xdl_mark_ignorable_regex(xdchange_t *xscr, const xdfenv_t *xe,
xdchange_t *xch;
for (xch = xscr; xch; xch = xch->next) {
- xrecord_t **rec;
+ xrecord_t *rec;
int ignore = 1;
long i;
@@ -1043,13 +1043,13 @@ static void xdl_mark_ignorable_regex(xdchange_t *xscr, const xdfenv_t *xe,
if (xch->ignore)
continue;
- rec = &xe->xdf1.recs[xch->i1];
+ rec = &xe->xdf1.record.ptr[xch->i1];
for (i = 0; i < xch->chg1 && ignore; i++)
- ignore = record_matches_regex(rec[i], xpp);
+ ignore = record_matches_regex(&rec[i], xpp);
- rec = &xe->xdf2.recs[xch->i2];
+ rec = &xe->xdf2.record.ptr[xch->i2];
for (i = 0; i < xch->chg2 && ignore; i++)
- ignore = record_matches_regex(rec[i], xpp);
+ ignore = record_matches_regex(&rec[i], xpp);
xch->ignore = ignore;
}
diff --git a/xdiff/xemit.c b/xdiff/xemit.c
index 11c2823eca..0c9a12a5e8 100644
--- a/xdiff/xemit.c
+++ b/xdiff/xemit.c
@@ -24,9 +24,9 @@
static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) {
- *rec = (char const*) xdf->recs[ri]->ptr;
+ *rec = (char const*) xdf->record.ptr[ri].ptr;
- return xdf->recs[ri]->size;
+ return xdf->record.ptr[ri].size;
}
diff --git a/xdiff/xhistogram.c b/xdiff/xhistogram.c
index 040d81e0bc..643d1c8b70 100644
--- a/xdiff/xhistogram.c
+++ b/xdiff/xhistogram.c
@@ -86,7 +86,7 @@ struct region {
((LINE_MAP(index, ptr))->cnt)
#define REC(env, s, l) \
- (env->xdf##s.recs[l - 1])
+ (&env->xdf##s.record.ptr[l - 1])
static int cmp_recs(xrecord_t *r1, xrecord_t *r2)
{
diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c
index f48549605d..0a3e0f28ab 100644
--- a/xdiff/xmerge.c
+++ b/xdiff/xmerge.c
@@ -97,12 +97,12 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,
int line_count, long flags)
{
int i;
- xrecord_t **rec1 = xe1->xdf2.recs + i1;
- xrecord_t **rec2 = xe2->xdf2.recs + i2;
+ xrecord_t *rec1 = xe1->xdf2.record.ptr + i1;
+ xrecord_t *rec2 = xe2->xdf2.record.ptr + i2;
for (i = 0; i < line_count; i++) {
- int result = xdl_recmatch((const char*) rec1[i]->ptr, rec1[i]->size,
- (const char*) rec2[i]->ptr, rec2[i]->size, flags);
+ int result = xdl_recmatch((const char*) rec1[i].ptr, rec1[i].size,
+ (const char*) rec2[i].ptr, rec2[i].size, flags);
if (!result)
return -1;
}
@@ -111,20 +111,20 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,
static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest)
{
- xrecord_t **recs;
+ xrecord_t *recs;
int size = 0;
- recs = (use_orig ? xe->xdf1.recs : xe->xdf2.recs) + i;
+ recs = (use_orig ? xe->xdf1.record.ptr : xe->xdf2.record.ptr) + i;
if (count < 1)
return 0;
- for (i = 0; i < count; size += recs[i++]->size)
+ for (i = 0; i < count; size += recs[i++].size)
if (dest)
- memcpy(dest + size, recs[i]->ptr, recs[i]->size);
+ memcpy(dest + size, recs[i].ptr, recs[i].size);
if (add_nl) {
- i = recs[count - 1]->size;
- if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') {
+ i = recs[count - 1].size;
+ if (i == 0 || recs[count - 1].ptr[i - 1] != '\n') {
if (needs_cr) {
if (dest)
dest[size] = '\r';
@@ -160,22 +160,22 @@ static int is_eol_crlf(xdfile_t *file, int i)
if (i < (isize) file->record.length - 1)
/* All lines before the last *must* end in LF */
- return (size = file->recs[i]->size) > 1 &&
- file->recs[i]->ptr[size - 2] == '\r';
+ return (size = file->record.ptr[i].size) > 1 &&
+ file->record.ptr[i].ptr[size - 2] == '\r';
if (!file->record.length)
/* Cannot determine eol style from empty file */
return -1;
- if ((size = file->recs[i]->size) &&
- file->recs[i]->ptr[size - 1] == '\n')
+ if ((size = file->record.ptr[i].size) &&
+ file->record.ptr[i].ptr[size - 1] == '\n')
/* Last line; ends in LF; Is it CR/LF? */
return size > 1 &&
- file->recs[i]->ptr[size - 2] == '\r';
+ file->record.ptr[i].ptr[size - 2] == '\r';
if (!i)
/* The only line has no eol */
return -1;
/* Determine eol from second-to-last line */
- return (size = file->recs[i - 1]->size) > 1 &&
- file->recs[i - 1]->ptr[size - 2] == '\r';
+ return (size = file->record.ptr[i - 1].size) > 1 &&
+ file->record.ptr[i - 1].ptr[size - 2] == '\r';
}
static int is_cr_needed(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m)
@@ -334,22 +334,22 @@ static int recmatch(xrecord_t *rec1, xrecord_t *rec2, unsigned long flags)
static void xdl_refine_zdiff3_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,
xpparam_t const *xpp)
{
- xrecord_t **rec1 = xe1->xdf2.recs, **rec2 = xe2->xdf2.recs;
+ xrecord_t *rec1 = xe1->xdf2.record.ptr, *rec2 = xe2->xdf2.record.ptr;
for (; m; m = m->next) {
/* let's handle just the conflicts */
if (m->mode)
continue;
while(m->chg1 && m->chg2 &&
- recmatch(rec1[m->i1], rec2[m->i2], xpp->flags)) {
+ recmatch(&rec1[m->i1], &rec2[m->i2], xpp->flags)) {
m->chg1--;
m->chg2--;
m->i1++;
m->i2++;
}
while (m->chg1 && m->chg2 &&
- recmatch(rec1[m->i1 + m->chg1 - 1],
- rec2[m->i2 + m->chg2 - 1], xpp->flags)) {
+ recmatch(&rec1[m->i1 + m->chg1 - 1],
+ &rec2[m->i2 + m->chg2 - 1], xpp->flags)) {
m->chg1--;
m->chg2--;
}
@@ -381,12 +381,12 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,
* This probably does not work outside git, since
* we have a very simple mmfile structure.
*/
- t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr;
- t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr
- + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - (u8 const*) t1.ptr;
- t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr;
- t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr
- + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - (u8 const*) t2.ptr;
+ t1.ptr = (char *)xe1->xdf2.record.ptr[m->i1].ptr;
+ t1.size = xe1->xdf2.record.ptr[m->i1 + m->chg1 - 1].ptr
+ + xe1->xdf2.record.ptr[m->i1 + m->chg1 - 1].size - (u8 const*) t1.ptr;
+ t2.ptr = (char *)xe2->xdf2.record.ptr[m->i2].ptr;
+ t2.size = xe2->xdf2.record.ptr[m->i2 + m->chg2 - 1].ptr
+ + xe2->xdf2.record.ptr[m->i2 + m->chg2 - 1].size - (u8 const*) t2.ptr;
if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0)
return -1;
if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 ||
@@ -440,8 +440,8 @@ static int line_contains_alnum(const char *ptr, long size)
static int lines_contain_alnum(xdfenv_t *xe, int i, int chg)
{
for (; chg; chg--, i++)
- if (line_contains_alnum((char const*) xe->xdf2.recs[i]->ptr,
- xe->xdf2.recs[i]->size))
+ if (line_contains_alnum((char const*) xe->xdf2.record.ptr[i].ptr,
+ xe->xdf2.record.ptr[i].size))
return 1;
return 0;
}
diff --git a/xdiff/xpatience.c b/xdiff/xpatience.c
index e1ce9a399f..31b819ec58 100644
--- a/xdiff/xpatience.c
+++ b/xdiff/xpatience.c
@@ -88,9 +88,9 @@ static int is_anchor(xpparam_t const *xpp, const char *line)
static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
int pass)
{
- xrecord_t **records = pass == 1 ?
- map->env->xdf1.recs : map->env->xdf2.recs;
- xrecord_t *record = records[line - 1];
+ xrecord_t *records = pass == 1 ?
+ map->env->xdf1.record.ptr : map->env->xdf2.record.ptr;
+ xrecord_t *record = &records[line - 1];
/*
* After xdl_prepare_env() (or more precisely, due to
* xdl_classify_record()), the "ha" member of the records (AKA lines)
@@ -121,7 +121,7 @@ static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
return;
map->entries[index].line1 = line;
map->entries[index].hash = record->ha;
- map->entries[index].anchor = is_anchor(xpp, (const char*) map->env->xdf1.recs[line - 1]->ptr);
+ map->entries[index].anchor = is_anchor(xpp, (const char*) map->env->xdf1.record.ptr[line - 1].ptr);
if (!map->first)
map->first = map->entries + index;
if (map->last) {
@@ -246,9 +246,9 @@ static int find_longest_common_sequence(struct hashmap *map, struct entry **res)
static int match(struct hashmap *map, int line1, int line2)
{
- xrecord_t *record1 = map->env->xdf1.recs[line1 - 1];
- xrecord_t *record2 = map->env->xdf2.recs[line2 - 1];
- return record1->ha == record2->ha;
+ u64 mph1 = map->env->xdf1.record.ptr[line1 - 1].ha;
+ u64 mph2 = map->env->xdf2.record.ptr[line2 - 1].ha;
+ return mph1 == mph2;
}
static int patience_diff(xpparam_t const *xpp, xdfenv_t *env,
diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c
index 9b46523afe..93370f1c6d 100644
--- a/xdiff/xprepare.c
+++ b/xdiff/xprepare.c
@@ -134,7 +134,6 @@ static void xdl_free_ctx(xdfile_t *xdf) {
xdl_free(xdf->rindex);
xdl_free(xdf->rchg - 1);
xdl_free(xdf->ha);
- xdl_free(xdf->recs);
}
@@ -147,7 +146,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, xpparam_t const *xpp
xdf->ha = NULL;
xdf->rindex = NULL;
xdf->rchg = NULL;
- xdf->recs = NULL;
IVEC_INIT(xdf->record);
if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
@@ -163,12 +161,9 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, xpparam_t const *xpp
}
ivec_shrink_to_fit(&xdf->record);
- if (!XDL_ALLOC_ARRAY(xdf->recs, xdf->record.length))
- goto abort;
for (usize i = 0; i < xdf->record.length; i++) {
if (xdl_classify_record(pass, cf, &xdf->record.ptr[i]) < 0)
goto abort;
- xdf->recs[i] = &xdf->record.ptr[i];
}
if (!XDL_CALLOC_ARRAY(xdf->rchg, xdf->record.length + 2))
@@ -267,7 +262,7 @@ static int xdl_clean_mmatch(char const *dis, long i, long s, long e) {
*/
static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
long i, nm, nreff, mlim;
- xrecord_t **recs;
+ xrecord_t *recs;
xdlclass_t *rcrec;
char *dis, *dis1, *dis2;
int need_min = !!(cf->flags & XDF_NEED_MINIMAL);
@@ -279,38 +274,38 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd
if ((mlim = xdl_bogosqrt(xdf1->record.length)) > XDL_MAX_EQLIMIT)
mlim = XDL_MAX_EQLIMIT;
- for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) {
- rcrec = cf->rcrecs[(*recs)->ha];
+ for (i = xdf1->dstart, recs = &xdf1->record.ptr[xdf1->dstart]; i <= xdf1->dend; i++, recs++) {
+ rcrec = cf->rcrecs[recs->ha];
nm = rcrec ? rcrec->len2 : 0;
dis1[i] = (nm == 0) ? 0: (nm >= mlim && !need_min) ? 2: 1;
}
if ((mlim = xdl_bogosqrt(xdf2->record.length)) > XDL_MAX_EQLIMIT)
mlim = XDL_MAX_EQLIMIT;
- for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {
- rcrec = cf->rcrecs[(*recs)->ha];
+ for (i = xdf2->dstart, recs = &xdf2->record.ptr[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {
+ rcrec = cf->rcrecs[recs->ha];
nm = rcrec ? rcrec->len1 : 0;
dis2[i] = (nm == 0) ? 0: (nm >= mlim && !need_min) ? 2: 1;
}
- for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart];
+ for (nreff = 0, i = xdf1->dstart, recs = &xdf1->record.ptr[xdf1->dstart];
i <= xdf1->dend; i++, recs++) {
if (dis1[i] == 1 ||
(dis1[i] == 2 && !xdl_clean_mmatch(dis1, i, xdf1->dstart, xdf1->dend))) {
xdf1->rindex[nreff] = i;
- xdf1->ha[nreff] = (*recs)->ha;
+ xdf1->ha[nreff] = recs->ha;
nreff++;
} else
xdf1->rchg[i] = 1;
}
xdf1->nreff = nreff;
- for (nreff = 0, i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart];
+ for (nreff = 0, i = xdf2->dstart, recs = &xdf2->record.ptr[xdf2->dstart];
i <= xdf2->dend; i++, recs++) {
if (dis2[i] == 1 ||
(dis2[i] == 2 && !xdl_clean_mmatch(dis2, i, xdf2->dstart, xdf2->dend))) {
xdf2->rindex[nreff] = i;
- xdf2->ha[nreff] = (*recs)->ha;
+ xdf2->ha[nreff] = recs->ha;
nreff++;
} else
xdf2->rchg[i] = 1;
@@ -328,21 +323,21 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd
*/
static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {
long i, lim;
- xrecord_t **recs1, **recs2;
+ xrecord_t *recs1, *recs2;
- recs1 = xdf1->recs;
- recs2 = xdf2->recs;
+ recs1 = xdf1->record.ptr;
+ recs2 = xdf2->record.ptr;
for (i = 0, lim = XDL_MIN(xdf1->record.length, xdf2->record.length); i < lim;
i++, recs1++, recs2++)
- if ((*recs1)->ha != (*recs2)->ha)
+ if (recs1->ha != recs2->ha)
break;
xdf1->dstart = xdf2->dstart = i;
- recs1 = xdf1->recs + xdf1->record.length - 1;
- recs2 = xdf2->recs + xdf2->record.length - 1;
+ recs1 = xdf1->record.ptr + xdf1->record.length - 1;
+ recs2 = xdf2->record.ptr + xdf2->record.length - 1;
for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--)
- if ((*recs1)->ha != (*recs2)->ha)
+ if (recs1->ha != recs2->ha)
break;
xdf1->dend = xdf1->record.length - i - 1;
diff --git a/xdiff/xtypes.h b/xdiff/xtypes.h
index c322e62fbf..849f218b32 100644
--- a/xdiff/xtypes.h
+++ b/xdiff/xtypes.h
@@ -49,7 +49,6 @@ DEFINE_IVEC_TYPE(xrecord_t, xrecord);
typedef struct s_xdfile {
struct ivec_xrecord record;
long dstart, dend;
- xrecord_t **recs;
char *rchg;
long *rindex;
long nreff;
diff --git a/xdiff/xutils.c b/xdiff/xutils.c
index 10e4f20b7c..eed88ee6cb 100644
--- a/xdiff/xutils.c
+++ b/xdiff/xutils.c
@@ -416,12 +416,12 @@ int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
mmfile_t subfile1, subfile2;
xdfenv_t env;
- subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr;
- subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr +
- diff_env->xdf1.recs[line1 + count1 - 2]->size - (u8 const*) subfile1.ptr;
- subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr;
- subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr +
- diff_env->xdf2.recs[line2 + count2 - 2]->size - (u8 const*) subfile2.ptr;
+ subfile1.ptr = (char *)diff_env->xdf1.record.ptr[line1 - 1].ptr;
+ subfile1.size = diff_env->xdf1.record.ptr[line1 + count1 - 2].ptr +
+ diff_env->xdf1.record.ptr[line1 + count1 - 2].size - (u8 const*) subfile1.ptr;
+ subfile2.ptr = (char *)diff_env->xdf2.record.ptr[line2 - 1].ptr;
+ subfile2.size = diff_env->xdf2.record.ptr[line2 + count2 - 2].ptr +
+ diff_env->xdf2.record.ptr[line2 + count2 - 2].size - (u8 const*) subfile2.ptr;
if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0)
return -1;
--
gitgitgadget
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 14/15] xdiff: make xdfile_t more rust friendly
2025-08-29 19:42 [PATCH 00/15] Introduce rust: In xdiff Ezekiel Newren via GitGitGadget
` (12 preceding siblings ...)
2025-08-29 19:42 ` [PATCH 13/15] xdiff: delete recs " Ezekiel Newren via GitGitGadget
@ 2025-08-29 19:42 ` Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 15/15] xdiff: implement xdl_trim_ends() in Rust Ezekiel Newren via GitGitGadget
14 siblings, 0 replies; 20+ messages in thread
From: Ezekiel Newren via GitGitGadget @ 2025-08-29 19:42 UTC (permalink / raw)
To: git; +Cc: Ezekiel Newren, Ezekiel Newren
From: Ezekiel Newren <ezekielnewren@gmail.com>
Convert the remaining ambiguous fields in xdfile_t from C types to Rust
types for interoperability.
Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
---
xdiff/xdiffi.c | 16 ++++++++--------
xdiff/xdiffi.h | 8 ++++----
xdiff/xtypes.h | 10 +++++-----
3 files changed, 17 insertions(+), 17 deletions(-)
diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c
index ebdb724322..0509b48759 100644
--- a/xdiff/xdiffi.c
+++ b/xdiff/xdiffi.c
@@ -42,8 +42,8 @@ typedef struct s_xdpsplit {
* using this algorithm, so a little bit of heuristic is needed to cut the
* search and to return a suboptimal point.
*/
-static long xdl_split(unsigned long const *ha1, long off1, long lim1,
- unsigned long const *ha2, long off2, long lim2,
+static long xdl_split(u64 const *ha1, long off1, long lim1,
+ u64 const *ha2, long off2, long lim2,
long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl,
xdalgoenv_t *xenv) {
long dmin = off1 - lim2, dmax = lim1 - off2;
@@ -260,7 +260,7 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1,
int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
diffdata_t *dd2, long off2, long lim2,
long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv) {
- unsigned long const *ha1 = dd1->ha, *ha2 = dd2->ha;
+ u64 const *ha1 = dd1->ha, *ha2 = dd2->ha;
/*
* Shrink the box by walking through each diagonal snake (SW and NE).
@@ -273,14 +273,14 @@ int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
* be obviously changed.
*/
if (off1 == lim1) {
- char *rchg2 = dd2->rchg;
- long *rindex2 = dd2->rindex;
+ u8 *rchg2 = dd2->rchg;
+ usize *rindex2 = dd2->rindex;
for (; off2 < lim2; off2++)
rchg2[rindex2[off2]] = 1;
} else if (off2 == lim2) {
- char *rchg1 = dd1->rchg;
- long *rindex1 = dd1->rindex;
+ u8 *rchg1 = dd1->rchg;
+ usize *rindex1 = dd1->rindex;
for (; off1 < lim1; off1++)
rchg1[rindex1[off1]] = 1;
@@ -944,7 +944,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr) {
xdchange_t *cscr = NULL, *xch;
- char *rchg1 = xe->xdf1.rchg, *rchg2 = xe->xdf2.rchg;
+ u8 *rchg1 = xe->xdf1.rchg, *rchg2 = xe->xdf2.rchg;
long i1, i2, l1, l2;
/*
diff --git a/xdiff/xdiffi.h b/xdiff/xdiffi.h
index 126c9d8ff4..c766ee115c 100644
--- a/xdiff/xdiffi.h
+++ b/xdiff/xdiffi.h
@@ -25,10 +25,10 @@
typedef struct s_diffdata {
- long nrec;
- unsigned long const *ha;
- long *rindex;
- char *rchg;
+ usize nrec;
+ u64 const *ha;
+ usize *rindex;
+ u8 *rchg;
} diffdata_t;
typedef struct s_xdalgoenv {
diff --git a/xdiff/xtypes.h b/xdiff/xtypes.h
index 849f218b32..66b3dfae8b 100644
--- a/xdiff/xtypes.h
+++ b/xdiff/xtypes.h
@@ -48,11 +48,11 @@ DEFINE_IVEC_TYPE(xrecord_t, xrecord);
typedef struct s_xdfile {
struct ivec_xrecord record;
- long dstart, dend;
- char *rchg;
- long *rindex;
- long nreff;
- unsigned long *ha;
+ isize dstart, dend;
+ u8 *rchg;
+ usize *rindex;
+ usize nreff;
+ u64 *ha;
} xdfile_t;
typedef struct s_xdfenv {
--
gitgitgadget
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 15/15] xdiff: implement xdl_trim_ends() in Rust
2025-08-29 19:42 [PATCH 00/15] Introduce rust: In xdiff Ezekiel Newren via GitGitGadget
` (13 preceding siblings ...)
2025-08-29 19:42 ` [PATCH 14/15] xdiff: make xdfile_t more rust friendly Ezekiel Newren via GitGitGadget
@ 2025-08-29 19:42 ` Ezekiel Newren via GitGitGadget
14 siblings, 0 replies; 20+ messages in thread
From: Ezekiel Newren via GitGitGadget @ 2025-08-29 19:42 UTC (permalink / raw)
To: git; +Cc: Ezekiel Newren, Ezekiel Newren
From: Ezekiel Newren <ezekielnewren@gmail.com>
Replace the C implementation of xdl_trim_ends() with a Rust
implementation.
Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
---
rust/xdiff/src/lib.rs | 15 +++++++++++++++
rust/xdiff/src/xprepare.rs | 27 +++++++++++++++++++++++++++
rust/xdiff/src/xtypes.rs | 19 +++++++++++++++++++
xdiff/xprepare.c | 28 +---------------------------
4 files changed, 62 insertions(+), 27 deletions(-)
create mode 100644 rust/xdiff/src/xprepare.rs
create mode 100644 rust/xdiff/src/xtypes.rs
diff --git a/rust/xdiff/src/lib.rs b/rust/xdiff/src/lib.rs
index e69de29bb2..4cc05a7e6b 100644
--- a/rust/xdiff/src/lib.rs
+++ b/rust/xdiff/src/lib.rs
@@ -0,0 +1,15 @@
+pub mod xprepare;
+pub mod xtypes;
+
+use crate::xprepare::trim_ends;
+use crate::xtypes::xdfile;
+
+#[no_mangle]
+unsafe extern "C" fn xdl_trim_ends(xdf1: *mut xdfile, xdf2: *mut xdfile) -> i32 {
+ let xdf1 = xdf1.as_mut().expect("null pointer");
+ let xdf2 = xdf2.as_mut().expect("null pointer");
+
+ trim_ends(xdf1, xdf2);
+
+ 0
+}
diff --git a/rust/xdiff/src/xprepare.rs b/rust/xdiff/src/xprepare.rs
new file mode 100644
index 0000000000..f64f60c099
--- /dev/null
+++ b/rust/xdiff/src/xprepare.rs
@@ -0,0 +1,27 @@
+use crate::xtypes::xdfile;
+
+///
+/// Early trim initial and terminal matching records.
+///
+pub(crate) fn trim_ends(xdf1: &mut xdfile, xdf2: &mut xdfile) {
+ let mut lim = std::cmp::min(xdf1.record.len(), xdf2.record.len());
+
+ for i in 0..lim {
+ if xdf1.record[i].ha != xdf2.record[i].ha {
+ xdf1.dstart = i as isize;
+ xdf2.dstart = i as isize;
+ lim -= i;
+ break;
+ }
+ }
+
+ for i in 0..lim {
+ let f1i = xdf1.record.len() - 1 - i;
+ let f2i = xdf2.record.len() - 1 - i;
+ if xdf1.record[f1i].ha != xdf2.record[f2i].ha {
+ xdf1.dend = f1i as isize;
+ xdf2.dend = f2i as isize;
+ break;
+ }
+ }
+}
diff --git a/rust/xdiff/src/xtypes.rs b/rust/xdiff/src/xtypes.rs
new file mode 100644
index 0000000000..3d1ce9742f
--- /dev/null
+++ b/rust/xdiff/src/xtypes.rs
@@ -0,0 +1,19 @@
+use interop::ivec::IVec;
+
+#[repr(C)]
+pub(crate) struct xrecord {
+ pub(crate) ptr: *const u8,
+ pub(crate) size: usize,
+ pub(crate) ha: u64,
+}
+
+#[repr(C)]
+pub(crate) struct xdfile {
+ pub(crate) record: IVec<xrecord>,
+ pub(crate) dstart: isize,
+ pub(crate) dend: isize,
+ pub(crate) rchg: *mut u8,
+ pub(crate) rindex: *mut usize,
+ pub(crate) nreff: usize,
+ pub(crate) ha: *mut u64,
+}
diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c
index 93370f1c6d..2c7480875f 100644
--- a/xdiff/xprepare.c
+++ b/xdiff/xprepare.c
@@ -318,33 +318,7 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd
}
-/*
- * Early trim initial and terminal matching records.
- */
-static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {
- long i, lim;
- xrecord_t *recs1, *recs2;
-
- recs1 = xdf1->record.ptr;
- recs2 = xdf2->record.ptr;
- for (i = 0, lim = XDL_MIN(xdf1->record.length, xdf2->record.length); i < lim;
- i++, recs1++, recs2++)
- if (recs1->ha != recs2->ha)
- break;
-
- xdf1->dstart = xdf2->dstart = i;
-
- recs1 = xdf1->record.ptr + xdf1->record.length - 1;
- recs2 = xdf2->record.ptr + xdf2->record.length - 1;
- for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--)
- if (recs1->ha != recs2->ha)
- break;
-
- xdf1->dend = xdf1->record.length - i - 1;
- xdf2->dend = xdf2->record.length - i - 1;
-
- return 0;
-}
+extern i32 xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2);
static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
--
gitgitgadget
^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 01/15] doc: add a policy for using Rust
2025-08-29 19:42 ` [PATCH 01/15] doc: add a policy for using Rust brian m. carlson via GitGitGadget
@ 2025-08-29 20:00 ` brian m. carlson
2025-08-29 20:11 ` Ezekiel Newren
0 siblings, 1 reply; 20+ messages in thread
From: brian m. carlson @ 2025-08-29 20:00 UTC (permalink / raw)
To: brian m. carlson via GitGitGadget; +Cc: git, Ezekiel Newren
[-- Attachment #1: Type: text/plain, Size: 2592 bytes --]
On 2025-08-29 at 19:42:05, brian m. carlson via GitGitGadget wrote:
> +Crates like libc or rustix define types like c_long, but in ways that are not
> +safe across platforms.
> +From https://docs.rs/rustix/latest/rustix/ffi/type.c_long.html:
> +
> + This type will always be i32 or i64. Most notably, many Linux-based
> + systems assume an i64, but Windows assumes i32. The C standard technically
> + only requires that this type be a signed integer that is at least 32 bits
> + and at least the size of an int, although in practice, no system would
> + have a long that is neither an i32 nor i64.
> +
> +Also, note that other locations, such as
> +https://docs.rs/libc/latest/libc/type.c_long.html, just hardcode c_long as i64
> +even though C may mean i32 on some platforms.
> +
> +As such, using the c_long type would give us portability issues, and
> +perpetuate some of the bugs git has faced across platforms. Avoid using C's
> +types (long, unsigned, char, etc.), and switch to unambiguous types (e.g. i32
> +or i64) before trying to make C and Rust interoperate.
This makes sense. I agree fixed-size types are better and less brittle.
> +Crates like libc and rustix may have also traditionally aided interoperability
> +with older versions of Rust (e.g. when worrying about stat[64] system calls),
> +but the Rust standard library in newer versions of Rust handle these concerns
> +in a platform agnostic way. There may arise cases where we need to consider
> +these crates, but for now we omit them.
I'm fine with omitting them for now. However, we may very well need
them in the future.
> +Tools like bindgen and cbindgen create C-styled unsafe Rust code rather than
> +idiomatic Rust; where possible, we prefer to switch to idiomatic Rust. Any
> +standard C library functions that are needed can be manually wrapped on the
> +Rust side.
I agree that we want to use idiomatic Rust whenever possible. However,
I don't want to define structures and function definitions in both
languages and rely on people keeping them in sync, since that's a great
way to create brittle, broken code. Very notably, I have seen these
kinds of misalignments break only on big-endian architectures, which
most of us do not use, so we can really end up causing problems that are
very subtle this way.
I would prefer we wrote these functions with cbindgen to avoid this. If
we define structures only in Rust and never ever use C structures, then
we can avoid bindgen.
--
brian m. carlson (they/them)
Toronto, Ontario, CA
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 262 bytes --]
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 01/15] doc: add a policy for using Rust
2025-08-29 20:00 ` brian m. carlson
@ 2025-08-29 20:11 ` Ezekiel Newren
2025-09-02 16:39 ` brian m. carlson
0 siblings, 1 reply; 20+ messages in thread
From: Ezekiel Newren @ 2025-08-29 20:11 UTC (permalink / raw)
To: brian m. carlson, brian m. carlson via GitGitGadget, git,
Ezekiel Newren
On Fri, Aug 29, 2025 at 2:00 PM brian m. carlson
<sandals@crustytoothpaste.net> wrote:
> > +Tools like bindgen and cbindgen create C-styled unsafe Rust code rather than
> > +idiomatic Rust; where possible, we prefer to switch to idiomatic Rust. Any
> > +standard C library functions that are needed can be manually wrapped on the
> > +Rust side.
>
> I agree that we want to use idiomatic Rust whenever possible. However,
> I don't want to define structures and function definitions in both
> languages and rely on people keeping them in sync, since that's a great
> way to create brittle, broken code. Very notably, I have seen these
> kinds of misalignments break only on big-endian architectures, which
> most of us do not use, so we can really end up causing problems that are
> very subtle this way.
>
> I would prefer we wrote these functions with cbindgen to avoid this. If
> we define structures only in Rust and never ever use C structures, then
> we can avoid bindgen.
Could you create a patch with your preferred wording about bindgen and
cbindgen for me? I'd prefer to introduce bindgen/cbindgen in a
different patch series because this one is already doing a lot just to
make Rust exist in Git.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 01/15] doc: add a policy for using Rust
2025-08-29 20:11 ` Ezekiel Newren
@ 2025-09-02 16:39 ` brian m. carlson
2025-09-02 18:39 ` Ezekiel Newren
0 siblings, 1 reply; 20+ messages in thread
From: brian m. carlson @ 2025-09-02 16:39 UTC (permalink / raw)
To: Ezekiel Newren; +Cc: brian m. carlson via GitGitGadget, git
[-- Attachment #1: Type: text/plain, Size: 848 bytes --]
On 2025-08-29 at 20:11:46, Ezekiel Newren wrote:
> Could you create a patch with your preferred wording about bindgen and
> cbindgen for me? I'd prefer to introduce bindgen/cbindgen in a
> different patch series because this one is already doing a lot just to
> make Rust exist in Git.
I think it's fine to introduce it in a different series. I'll plan to
do that myself if it doesn't get done sooner.
My change to the text is the following:
Tools like bindgen and cbindgen create C-styled unsafe Rust code rather than
idiomatic Rust; where possible, we prefer to switch to idiomatic Rust.
However, we may use bindgen and cbindgen to share existing Git types as an
interim step.
You can find the patch at https://github.com/bk2204/git.git in the
`rust-policy` branch.
--
brian m. carlson (they/them)
Toronto, Ontario, CA
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 262 bytes --]
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 01/15] doc: add a policy for using Rust
2025-09-02 16:39 ` brian m. carlson
@ 2025-09-02 18:39 ` Ezekiel Newren
0 siblings, 0 replies; 20+ messages in thread
From: Ezekiel Newren @ 2025-09-02 18:39 UTC (permalink / raw)
To: brian m. carlson, Ezekiel Newren,
brian m. carlson via GitGitGadget, git
On Tue, Sep 2, 2025 at 10:39 AM brian m. carlson
<sandals@crustytoothpaste.net> wrote:
> I think it's fine to introduce it in a different series. I'll plan to
> do that myself if it doesn't get done sooner.
Actually now that I'm knee deep in adding cbindgen to Rust it'll make
less sense to add it later. I'm currently working on refactoring my
entire patch series to include cbindgen from the beginning. I haven't
looked into cbindgen until now because I wanted to understand at a
deep level how C <-> Rust ffi worked rather than using an automagical
tool like cbindgen. I now think cbindgen should be part of the
introduction of Rust.
^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2025-09-02 18:39 UTC | newest]
Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-29 19:42 [PATCH 00/15] Introduce rust: In xdiff Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 01/15] doc: add a policy for using Rust brian m. carlson via GitGitGadget
2025-08-29 20:00 ` brian m. carlson
2025-08-29 20:11 ` Ezekiel Newren
2025-09-02 16:39 ` brian m. carlson
2025-09-02 18:39 ` Ezekiel Newren
2025-08-29 19:42 ` [PATCH 02/15] xdiff: introduce rust Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 03/15] github workflows: install rust Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 04/15] win+Meson: do allow linking with the Rust-built xdiff Johannes Schindelin via GitGitGadget
2025-08-29 19:42 ` [PATCH 05/15] github workflows: upload Cargo.lock Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 06/15] ivec: create a vector type that is interoperable between C and Rust Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 07/15] xdiff/xprepare: remove superfluous forward declarations Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 08/15] xdiff: delete unnecessary fields from xrecord_t and xdfile_t Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 09/15] xdiff: make fields of xrecord_t Rust friendly Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 10/15] xdiff: use one definition for freeing xdfile_t Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 11/15] xdiff: replace chastore with an ivec in xdfile_t Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 12/15] xdiff: delete nrec field from xdfile_t Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 13/15] xdiff: delete recs " Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 14/15] xdiff: make xdfile_t more rust friendly Ezekiel Newren via GitGitGadget
2025-08-29 19:42 ` [PATCH 15/15] xdiff: implement xdl_trim_ends() in Rust Ezekiel Newren via GitGitGadget
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).