iOS13 PostFix #5: implementation of missing Struct.offsetOf
17 Nov 2019 | fix compiler postfixThis 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:
- PostFix #1: Generic class arguments and @Block parameters
- PostFix #2: Adding support for non-static @Bridge method in enums classes
- PostFix #3: Support for @Block member in structs
- PostFix #4: Compilation failed on @Bridge annotate covariant return synthetic method
- PostFix #6: Fixes to Network framework bindings
- PostFix #7: bindings for ios13.2
- PostFix #8: workaround for missing objc classes(ObjCClassNotFoundException)
- PostFix #9: experimental and formal bitcode support
- PostFix #10: glkit – missing functions (static inline now)
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);
moduleBuilder.addFunction(fn);
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.newBasicBlock(switchLabels[idx]);
fn.add(new Ret(new IntegerConstant(offsets[idx])));
}
// default case -- array out of bounds exception
fn.newBasicBlock(def);
}
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 {
label0:
switch i32 %p1, label %label3 [ i32 0, label %label1 i32 1, label %label2 ]
label1:
ret i32 0
label2:
ret i32 8
label3:
call void @"_bcThrowArrayIndexOutOfBoundsException"(%Env* %p0, i32 2, i32 %p1)
unreachable
}
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
Comments