Skip to content

Separating barrier policies and barrier mechanisms #1406

@wks

Description

@wks

In 24 September 2025, we discussed the difference between "barrier policies" and "barrier implementations".

A barrier mechanism, in simple terms, can be an object-logging write barrier, a field-logging write barrier, a read barrier for reading weak fields, etc, and it can be pre-barriers or post-barriers.

On the other hand, a barrier policy is a combination of barrier implementations. For example, to implement the SATB-based ConcurrentImmix plan, we need the VM to implement either the object-logging pre-write barrier that records all fields, or the field-logging pre-write barrier that records the single modified field, and we also need the VM to apply a read barrier when reading from weak fields.

Status quo

Currently, implementations of trait Barrier are actually barrier policies. For example, SATBBarrier combines both a load barrier and a pre-write barrier, implemented as different methods of SATBBarrier. Its pre-write barrier currently has object granularity.

If we add LXR, it will need to combine three barrier mechanisms.

Granularity of barrier mechanisms

A barrier mechanism can operate on a granularity

  • whole object (e.g. object-logging barrier)
  • single field (e.g. field-logging barrier)
  • a slice

What operations to apply?

Depending on VMs, there are different operations to apply barriers to. For example,

  • Writing to a scalar field (astore in JVM)
  • Writing to an array element (aastore in JVM)
  • Bulk clearing/setting/copying array elements (arraycopy in JVM)
  • Reading from a weak reference filed (Reference.get() except PhantomReference.get() which always returns null).

One key aspect of separating mechanisms from policies is that we can apply different mechanisms to different operations.

For example, in OpenJDK, field accesses like obj.field = val usually have access to the obj address, so we can use either object-logging or field-logging barriers to implement the SATB barrier and the generational remembered set barrier (the so-called ObjectBarrier); but arraycopy does not give the JIT compiler the base address, so we have to use field-grained or slice-grained barrier mechanisms that does not depend on the base address.

  • OpenJDK
    • Object fields: object-grained or field-grained
    • Single array element: object-grained or field-grained
    • Arraycopy: slice-grained

As another example, CRuby gives the interpreter field addresses and array element addresses for T_OBJECT ("ordinary" Ruby objects) and T_ARRAY (Ruby arrays), so we can use field-grained barriers for those types. However, the vast majority of other types are implemented in C, and some C code accesses objects (including T_OBJECT and T_ARRAY) using built-in C functions. Sometimes the C code just modifies some fields and calls rb_gc_writebarrier_remember(obj) on the whole object. We have to use object-grained barriers for those types.

  • CRuby
    • Accessing T_OBJECT fields and T_ARRAY elements from Ruby: object-grained or field-grained
    • Custom types implemented in C, or field accesses in C without field addresses: object-grained

Implications for plans

Most plans that need barriers can work with either object-grained or field/slice-grained mechanisms. It has two implications:

  1. Plans need to be implemented in a general way so that VMs can choose to use object-grained or field-grained barriers.
  2. They need a specification so that VM binding developers know which barrier mechanisms to implement in the fast paths, such as SATB barriers or object-remembering (remembered set) barriers.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions