blob: 367c901831a48abe00c345ff8e5a6bbabdbff32b [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.apf;
import static android.net.apf.BaseApfGenerator.MemorySlot;
import static android.net.apf.BaseApfGenerator.Register.R0;
import android.annotation.NonNull;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
/**
* A table that stores program labels to jump to.
*
* This is needed to implement subroutines because APF jump targets must be known at compile
* time and cannot be computed dynamically.
*
* At compile time, any code that calls a subroutine must:
*
* <ul>
* <li>Define a label (via {@link ApfV4Generator#defineLabel}) immediately after the code that
* invokes the subroutine.
* <li>Add the label to the jump table using {@link #addLabel}.
* <li>Generate the jump table in the program.
* </ul>
*
* <p>At runtime, before invoking the subroutine, the APF code must store the index of the return
* label (obtained via {@link #getIndex}) into the jump table's return address memory slot, and then
* jump to the subroutine. To return to the caller, the subroutine must jump to the label returned
* by {@link #getStartLabel}, and the jump table will then jump to the return label.
*
* <p>Implementation details:
* <ul>
* <li>The jumps are added to the program in the same order as the labels were added.
* <li>Using the jump table will overwrite the value of register R0.
* <li>If, before calling a subroutine, the APF code stores a nonexistent return label index, then
* the jump table will pass the packet. This cannot happen if the code correctly obtains the
* label using {@link #getIndex}, as that would throw an exception when generating the program.
* </ul>
*
* For example:
* <pre>
* JumpTable t = new JumpTable("my_jump_table", 7);
* t.addLabel("jump_1");
* ...
* t.addLabel("after_parsing");
* ...
* t.addLabel("after_subroutine");
* t.generate(gen);
*</pre>
* generates the following APF code:
* <pre>
* :my_jump_table
* ldm r0, 7
* jeq r0, 0, jump_1
* jeq r0, 1, after_parsing
* jeq r0, 2, after_subroutine
* jmp DROP
* </pre>
*/
public class JumpTable {
/** Maps jump indices to jump labels. LinkedHashMap guarantees iteration in insertion order. */
private final Map<String, Integer> mJumpLabels = new LinkedHashMap<>();
/** Label to jump to to execute this jump table. */
private final String mStartLabel;
/** Memory slot that contains the return value index. */
private final MemorySlot mReturnAddressMemorySlot;
private int mIndex = 0;
public JumpTable(@NonNull String startLabel, MemorySlot returnAddressMemorySlot) {
Objects.requireNonNull(startLabel);
mStartLabel = startLabel;
if (returnAddressMemorySlot.value < 0
|| returnAddressMemorySlot.value >= MemorySlot.FIRST_PREFILLED.value) {
throw new IllegalArgumentException(
"Invalid memory slot " + returnAddressMemorySlot.value);
}
mReturnAddressMemorySlot = returnAddressMemorySlot;
}
/** Returns the label to jump to to start executing the table. */
@NonNull
public String getStartLabel() {
return mStartLabel;
}
/**
* Adds a jump label to this table. Passing a label that was already added is not an error.
*
* @param label the label to add
*/
public void addLabel(@NonNull String label) {
Objects.requireNonNull(label);
if (mJumpLabels.putIfAbsent(label, mIndex) == null) mIndex++;
}
/**
* Gets the index of a previously-added label.
* @return the label's index.
* @throws NoSuchElementException if the label was never added.
*/
public int getIndex(@NonNull String label) {
final Integer index = mJumpLabels.get(label);
if (index == null) throw new NoSuchElementException("Unknown label " + label);
return index;
}
/** Generates APF code for this jump table */
public void generate(@NonNull ApfV4Generator gen)
throws ApfV4Generator.IllegalInstructionException {
gen.defineLabel(mStartLabel);
gen.addLoadFromMemory(R0, mReturnAddressMemorySlot);
for (Map.Entry<String, Integer> e : mJumpLabels.entrySet()) {
gen.addJumpIfR0Equals(e.getValue(), e.getKey());
}
// Cannot happen unless the program is malformed (i.e., the APF code loads an invalid return
// label index before jumping to the subroutine.
gen.addJump(ApfV4Generator.PASS_LABEL);
}
}