RFR: Improve formatting of generated source code

Jorn Vernee jvernee at openjdk.org
Fri Dec 15 14:30:36 UTC 2023


As a main rule-of-thumb: we should generate a blank line before a multi-line declaration, or before a block of single line declarations (e.g. fields, or methods collapsed into a single line).

In most cases, we can just add a blank line to the start of the String template we use. In some cases that emit single-line declarations, we have a manual call to `appendBlankLine` before emitting multiple single line decls. For declarations with comments we also add the blank line manually, as there shouldn't be a blank line between the comment and declaration.

- I've also added the default constructor to every class we generate, and moved it before the layout decl in struct classes (makes a little bit more sense I think).
- fixed the indentation and excess trailing whitespace after the class `}` of a class.
- Fixed the way in which we add indentation to text blocks, so that we don't add trailing white space to empty lines.

<details>
<summary>Sample output (click to unfold)</summary>

For instance for this header:

struct Foo {
    int x;
    struct Bar {
        int y;
    } bar;
};

typedef struct Foo (*CB)(void);


Foo.java:

// Generated by jextract

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;
import java.lang.foreign.*;
import static java.lang.foreign.ValueLayout.*;

/**
 * {@snippet lang=c :
 * struct Foo {
 *     int x;
 *     struct Bar bar;
 * };
 * }
 */
public class Foo {

    Foo() {
        // Suppresses public default constructor, ensuring non-instantiability,
        // but allows generated subclasses in same package.
    }

    private static final GroupLayout $LAYOUT = MemoryLayout.structLayout(
        struct_with_fptr_h.C_INT.withName("x"),
        Foo.Bar.$LAYOUT().withName("bar")
    ).withName("Foo");

    public static final GroupLayout $LAYOUT() {
        return $LAYOUT;
    }

    private static final long x$OFFSET = 0;

    /**
     * Getter for field:
     * {@snippet lang=c :
     * int x;
     * }
     */
    public static int x$get(MemorySegment seg) {
        return seg.get(struct_with_fptr_h.C_INT, x$OFFSET);
    }

    public static int x$get(MemorySegment seg, long index) {
        return seg.get(struct_with_fptr_h.C_INT, x$OFFSET + (index * sizeof()));
    }

    /**
     * Setter for field:
     * {@snippet lang=c :
     * int x;
     * }
     */
    public static void x$set(MemorySegment seg, int x) {
        seg.set(struct_with_fptr_h.C_INT, x$OFFSET, x);
    }

    public static void x$set(MemorySegment seg, long index, int x) {
        seg.set(struct_with_fptr_h.C_INT, x$OFFSET + (index * sizeof()), x);
    }

    /**
     * {@snippet lang=c :
     * struct Bar {
     *     int y;
     * };
     * }
     */
    public static class Bar {

        Bar() {
            // Suppresses public default constructor, ensuring non-instantiability,
            // but allows generated subclasses in same package.
        }

        private static final GroupLayout $LAYOUT = MemoryLayout.structLayout(
            struct_with_fptr_h.C_INT.withName("y")
        ).withName("Bar");

        public static final GroupLayout $LAYOUT() {
            return $LAYOUT;
        }

        private static final long y$OFFSET = 0;

        /**
         * Getter for field:
         * {@snippet lang=c :
         * int y;
         * }
         */
        public static int y$get(MemorySegment seg) {
            return seg.get(struct_with_fptr_h.C_INT, y$OFFSET);
        }

        public static int y$get(MemorySegment seg, long index) {
            return seg.get(struct_with_fptr_h.C_INT, y$OFFSET + (index * sizeof()));
        }

        /**
         * Setter for field:
         * {@snippet lang=c :
         * int y;
         * }
         */
        public static void y$set(MemorySegment seg, int x) {
            seg.set(struct_with_fptr_h.C_INT, y$OFFSET, x);
        }

        public static void y$set(MemorySegment seg, long index, int x) {
            seg.set(struct_with_fptr_h.C_INT, y$OFFSET + (index * sizeof()), x);
        }

        public static long sizeof() { return $LAYOUT().byteSize(); }
        public static MemorySegment allocate(SegmentAllocator allocator) { return allocator.allocate($LAYOUT()); }

        public static MemorySegment allocateArray(long len, SegmentAllocator allocator) {
            return allocator.allocate(MemoryLayout.sequenceLayout(len, $LAYOUT()));
        }

        public static MemorySegment ofAddress(MemorySegment addr, Arena scope) {
            return addr.reinterpret($LAYOUT().byteSize(), scope, null);
        }
    }

    private static final long bar$OFFSET = 4;
    private static final long bar$SIZE = 4;

    public static MemorySegment bar$slice(MemorySegment seg) {
        return seg.asSlice(bar$OFFSET, bar$SIZE);
    }

    public static long sizeof() { return $LAYOUT().byteSize(); }
    public static MemorySegment allocate(SegmentAllocator allocator) { return allocator.allocate($LAYOUT()); }

    public static MemorySegment allocateArray(long len, SegmentAllocator allocator) {
        return allocator.allocate(MemoryLayout.sequenceLayout(len, $LAYOUT()));
    }

    public static MemorySegment ofAddress(MemorySegment addr, Arena scope) {
        return addr.reinterpret($LAYOUT().byteSize(), scope, null);
    }
}


CB.java:

// Generated by jextract

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;
import java.lang.foreign.*;
import static java.lang.foreign.ValueLayout.*;

/**
 * {@snippet lang=c :
 * struct Foo (*CB)();
 * }
 */
public interface CB {

    MemorySegment apply();

    FunctionDescriptor $DESC = FunctionDescriptor.of(
        Foo.$LAYOUT());

    MethodHandle UP$MH = struct_with_fptr_h.upcallHandle(CB.class, "apply", $DESC);

    static MemorySegment allocate(CB fi, Arena scope) {
        return Linker.nativeLinker().upcallStub(UP$MH.bindTo(fi), $DESC, scope);
    }

    MethodHandle DOWN$MH = Linker.nativeLinker().downcallHandle($DESC);

    static CB ofAddress(MemorySegment addr, Arena arena) {
        MemorySegment symbol = addr.reinterpret(arena, null);
        return () -> {
            try {
                return (MemorySegment) DOWN$MH.invokeExact(symbol, (SegmentAllocator)arena);
            } catch (Throwable ex$) {
                throw new AssertionError("should not reach here", ex$);
            }
        };
    }
}


struct_with_fptr_h.java:

// Generated by jextract

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;
import java.lang.foreign.*;
import static java.lang.foreign.ValueLayout.*;

public class struct_with_fptr_h {

    static final SymbolLookup SYMBOL_LOOKUP;
    static {
        SymbolLookup loaderLookup = SymbolLookup.loaderLookup();
        Linker linker = Linker.nativeLinker();
        SYMBOL_LOOKUP = name -> loaderLookup.find(name).or(() -> linker.defaultLookup().find(name));
    }

    struct_with_fptr_h() {
        // Suppresses public default constructor, ensuring non-instantiability,
        // but allows generated subclasses in same package.
    }

    public static final ValueLayout.OfBoolean C_BOOL = ValueLayout.JAVA_BOOLEAN;
    public static final ValueLayout.OfByte C_CHAR = ValueLayout.JAVA_BYTE;
    public static final ValueLayout.OfShort C_SHORT = ValueLayout.JAVA_SHORT;
    public static final ValueLayout.OfInt C_INT = ValueLayout.JAVA_INT;
    public static final ValueLayout.OfLong C_LONG_LONG = ValueLayout.JAVA_LONG;
    public static final ValueLayout.OfFloat C_FLOAT = ValueLayout.JAVA_FLOAT;
    public static final ValueLayout.OfDouble C_DOUBLE = ValueLayout.JAVA_DOUBLE;
    public static final AddressLayout C_POINTER = ValueLayout.ADDRESS
            .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, JAVA_BYTE));
    public static final ValueLayout.OfInt C_LONG = ValueLayout.JAVA_INT;
    public static final ValueLayout.OfDouble C_LONG_DOUBLE = ValueLayout.JAVA_DOUBLE;

    static MemorySegment findOrThrow(String symbol) {
        return SYMBOL_LOOKUP.find(symbol)
            .orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol: " + symbol));
    }

    static MemoryLayout[] inferVariadicLayouts(Object[] varargs) {
        MemoryLayout[] result = new MemoryLayout[varargs.length];
        for (int i = 0; i < varargs.length; i++) {
            result[i] = variadicLayout(varargs[i].getClass());
        }
        return result;
    }

    static MethodHandle upcallHandle(Class<?> fi, String name, FunctionDescriptor fdesc) {
        try {
            return MethodHandles.lookup().findVirtual(fi, name, fdesc.toMethodType());
        } catch (ReflectiveOperationException ex) {
            throw new AssertionError(ex);
        }
    }

    static MethodHandle downcallHandleVariadic(String name, FunctionDescriptor baseDesc, MemoryLayout[] variadicLayouts) {
        FunctionDescriptor variadicDesc = baseDesc.appendArgumentLayouts(variadicLayouts);
        Linker.Option fva = Linker.Option.firstVariadicArg(baseDesc.argumentLayouts().size());
        return SYMBOL_LOOKUP.find(name)
                .map(addr -> Linker.nativeLinker().downcallHandle(addr, variadicDesc, fva)
                        .asSpreader(Object[].class, variadicLayouts.length))
                .orElse(null);
    }

    // Internals only below this point

    private static MemoryLayout variadicLayout(Class<?> c) {
        // apply default argument promotions per C spec
        // note that all primitives are boxed, since they are passed through an Object[]
        if (c == Boolean.class || c == Byte.class || c == Character.class || c == Short.class || c == Integer.class) {
            return JAVA_INT;
        } else if (c == Long.class) {
            return JAVA_LONG;
        } else if (c == Float.class || c == Double.class) {
            return JAVA_DOUBLE;
        } else if (MemorySegment.class.isAssignableFrom(c)) {
            return ADDRESS;
        }
        throw new IllegalArgumentException("Invalid type for ABI: " + c.getTypeName());
    }
}

</details>

-------------

Commit messages:
 - switch constructor and first header preamble
 - add newline before constructor
 - polish
 - improve blank link usage in generated sources
 - fix indentation and placement of private default constructor
 - don't indent closing brace of class decl
 - remove indentation of trailing whitespace

Changes: https://git.openjdk.org/jextract/pull/166/files
 Webrev: https://webrevs.openjdk.org/?repo=jextract&pr=166&range=00
  Stats: 68 lines in 7 files changed: 45 ins; 7 del; 16 mod
  Patch: https://git.openjdk.org/jextract/pull/166.diff
  Fetch: git fetch https://git.openjdk.org/jextract.git pull/166/head:pull/166

PR: https://git.openjdk.org/jextract/pull/166


More information about the jextract-dev mailing list