Few cents about my commits

iOS13 PostFix #5: implementation of missing Struct.offsetOf


This post continues the series of fixes discovered during compilation of CocoaTouch library and improvements to compiler.

PostFix #5: Struct.offsetOf always returns zero

Other postfixes:

Struct.offsetOf is required to proper implement initialization of variable size structs with flexible array member such as:

struct vectord {
    size_t len;
    double arr[]; // the flexible array member must be last

Root case and fix

Struct has offsetOf definition and it returns always zero similar to sizeOf method. `` its implemenation is being synthesized by RoboVM compiler and invocation of sizeOf being fixed during trampoline phase from Struct.sizeOf to DestStruct.sizeOf (DestStruct – an example struct implementation). Root case – compiler doesn’t not synthesize offsetOf.

Obtaining memeber offset

Structure member offsset is to retrieved from prepared LLVM structure definition using LLVMOffsetOfElement. Code is simple – just need to call it for eatch member:

public int[] getStructMemberOffsets(StructureType structType) {
    // get offset of each struct member by calling llvm api
    int membersCount = structType.getTypeCount() - structType.getOwnMembersOffset();
    int offset = structType.getOwnMembersOffset(); // inherited struct (if any) goes as member 0, own members starts 1
    int[] offsets = new int[membersCount];
    for (int idx = 0; idx < membersCount; idx++)
        offsets[idx] = config.getDataLayout().getOffsetOfElement(structType, offset + idx);
    return offsets;

Synthesize offsetOf

offsetOf is simple function of kind: switch(memberIdx) -> return offset. All constants are pre-calculated during compilation and funciton just returns constants. Code that synthesize offsetOf looks like bellow:

private Function structOffsetOf(ModuleBuilder moduleBuilder, SootMethod method) {
    Function fn = createMethodFunction(method);

    int[] offsets = getStructMemberOffsets(structType);
    if (offsets.length > 0 ) {
        // function code -- basic switch of returns
        Label[] switchLabels = new Label[offsets.length];
        Map<IntegerConstant, BasicBlockRef> targets = new HashMap<IntegerConstant, BasicBlockRef>();
        for (int idx = 0; idx < offsets.length; idx++) {
            targets.put(new IntegerConstant(idx), fn.newBasicBlockRef(switchLabels[idx] = new Label(idx)));

        Value idxValue = fn.getParameterRef(1); // 'env' is parameter 0
        Label def = new Label(-1);
        fn.add(new Switch(idxValue, fn.newBasicBlockRef(def), targets));

        // cases
        for (int idx = 0; idx < offsets.length; idx++) {
            fn.add(new Ret(new IntegerConstant(offsets[idx])));

        // default case -- array out of bounds exception
    Functions.call(fn, Functions.BC_THROW_ARRAY_INDEX_OUT_OF_BOUNDS_EXCEPTION, fn.getParameterRef(0),
        new IntegerConstant(offsets.length), fn.getParameterRef(1));
    fn.add(new Unreachable());

    return fn;

It produces offsetOf LLVM IR code similar to bellow:

define weak i32 @"[J]com.example.Testic5.offsetOf(I)I"(%Env* %p0, i32 %p1) nounwind noinline optsize {
    switch i32 %p1, label %label3 [ i32 0, label %label1 i32 1, label %label2 ]
    ret i32 0
    ret i32 8
    call void @"_bcThrowArrayIndexOutOfBoundsException"(%Env* %p0, i32 2, i32 %p1)

Trampoline for offsetOf

For every MyStruct.offsetOf javac generates call to Struct.offsetOf and last is always return zero. offsetOf was synthesized for MyStruct with changes above but this happened after javac and it resolve offsetOf to Struct as it was not hidden in MyStruct. Solution for this is ultimately trampoline all offsetOf calls to final struct. Same is already being done for sizeOf method in TrampolineCompiler.java:

Source code

The fix was delivered as PR431