blob: 6dcc33882e4d8a64e56c63940569efbf92c422ef [file] [log] [blame]
/*
* Copyright (C) 2024 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.Rbit.Rbit0;
import static android.net.apf.BaseApfGenerator.Register.R0;
import static android.net.apf.BaseApfGenerator.Register.R1;
import android.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Objects;
/**
* APF assembler/generator. A tool for generating an APF program.
*
* Call add*() functions to add instructions to the program, then call
* {@link BaseApfGenerator#generate} to get the APF bytecode for the program.
* <p>
* Choose between these approaches for your instruction helper methods: If the functionality must
* be identical across APF versions, make it a final method within the base class. If it needs
* version-specific adjustments, use an abstract method in the base class with final
* implementations in generator instances.
*
* @param <Type> the generator class
*
* @hide
*/
public abstract class ApfV4GeneratorBase<Type extends ApfV4GeneratorBase<Type>> extends
BaseApfGenerator {
/**
* Creates an ApfV4GeneratorBase instance which is able to emit instructions for the specified
* {@code version} of the APF interpreter. Throws {@code IllegalInstructionException} if
* the requested version is unsupported.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public ApfV4GeneratorBase(int version, boolean disableCounterRangeCheck)
throws IllegalInstructionException {
super(version, disableCounterRangeCheck);
requireApfVersion(APF_VERSION_2);
}
final Type self() {
return (Type) this;
}
final Type append(Instruction instruction) {
if (mGenerated) {
throw new IllegalStateException("Program already generated");
}
mInstructions.add(instruction);
return self();
}
/**
* Define a label at the current end of the program. Jumps can jump to this label. Labels are
* their own separate instructions, though with size 0. This facilitates having labels with
* no corresponding code to execute, for example a label at the end of a program. For example
* an {@link ApfV4GeneratorBase} might be passed to a function that adds a filter like so:
* <pre>
* load from packet
* compare loaded data, jump if not equal to "next_filter"
* load from packet
* compare loaded data, jump if not equal to "next_filter"
* jump to drop label
* define "next_filter" here
* </pre>
* In this case "next_filter" may not have any generated code associated with it.
*/
public final Type defineLabel(String name) throws IllegalInstructionException {
return append(new Instruction(Opcodes.LABEL).setLabel(name));
}
/**
* Add an unconditional jump instruction to the end of the program.
*/
public final Type addJump(String target) {
return append(new Instruction(Opcodes.JMP).setTargetLabel(target));
}
/**
* Add an unconditional jump instruction to the next instruction - ie. a no-op.
*/
public final Type addNop() {
return append(new Instruction(Opcodes.JMP).addUnsigned(0));
}
/**
* Add an instruction to the end of the program to load the byte at offset {@code offset}
* bytes from the beginning of the packet into {@code register}.
*/
public final Type addLoad8(Register r, int ofs) {
return append(new Instruction(Opcodes.LDB, r).addPacketOffset(ofs));
}
/**
* Add an instruction to the end of the program to load 16-bits at offset {@code offset}
* bytes from the beginning of the packet into {@code register}.
*/
public final Type addLoad16(Register r, int ofs) {
return append(new Instruction(Opcodes.LDH, r).addPacketOffset(ofs));
}
/**
* Add an instruction to the end of the program to load 32-bits at offset {@code offset}
* bytes from the beginning of the packet into {@code register}.
*/
public final Type addLoad32(Register r, int ofs) {
return append(new Instruction(Opcodes.LDW, r).addPacketOffset(ofs));
}
/**
* Add an instruction to the end of the program to load a byte from the packet into
* {@code register}. The offset of the loaded byte from the beginning of the packet is
* the sum of {@code offset} and the value in register R1.
*/
public final Type addLoad8Indexed(Register r, int ofs) {
return append(new Instruction(Opcodes.LDBX, r).addTwosCompUnsigned(ofs));
}
/**
* Add an instruction to the end of the program to load 16-bits from the packet into
* {@code register}. The offset of the loaded 16-bits from the beginning of the packet is
* the sum of {@code offset} and the value in register R1.
*/
public final Type addLoad16Indexed(Register r, int ofs) {
return append(new Instruction(Opcodes.LDHX, r).addTwosCompUnsigned(ofs));
}
/**
* Add an instruction to the end of the program to load 32-bits from the packet into
* {@code register}. The offset of the loaded 32-bits from the beginning of the packet is
* the sum of {@code offset} and the value in register R1.
*/
public final Type addLoad32Indexed(Register r, int ofs) {
return append(new Instruction(Opcodes.LDWX, r).addTwosCompUnsigned(ofs));
}
/**
* Add an instruction to the end of the program to add {@code value} to register R0.
*/
public final Type addAdd(long val) {
if (val == 0) return self(); // nop, as APFv6 would '+= R1'
return append(new Instruction(Opcodes.ADD).addTwosCompUnsigned(val));
}
/**
* Add an instruction to the end of the program to subtract {@code value} from register R0.
*/
public final Type addSub(long val) {
return addAdd(-val); // note: addSub(4 billion) isn't valid, as addAdd(-4 billion) isn't
}
/**
* Add an instruction to the end of the program to multiply register R0 by {@code value}.
*/
public final Type addMul(long val) {
if (val == 0) return addLoadImmediate(R0, 0); // equivalent, as APFv6 would '*= R1'
return append(new Instruction(Opcodes.MUL).addUnsigned(val));
}
/**
* Add an instruction to the end of the program to divide register R0 by {@code value}.
*/
public final Type addDiv(long val) {
if (val == 0) return addPass(); // equivalent, as APFv6 would '/= R1'
return append(new Instruction(Opcodes.DIV).addUnsigned(val));
}
/**
* Add an instruction to the end of the program to logically and register R0 with {@code value}.
*/
public final Type addAnd(long val) {
if (val == 0) return addLoadImmediate(R0, 0); // equivalent, as APFv6 would '+= R1'
return append(new Instruction(Opcodes.AND).addTwosCompUnsigned(val));
}
/**
* Add an instruction to the end of the program to logically or register R0 with {@code value}.
*/
public final Type addOr(long val) {
if (val == 0) return self(); // nop, as APFv6 would '|= R1'
return append(new Instruction(Opcodes.OR).addTwosCompUnsigned(val));
}
/**
* Add an instruction to the end of the program to shift left register R0 by {@code value} bits.
*/
// TODO: consider whether should change the argument type to byte
public final Type addLeftShift(int val) {
if (val == 0) return self(); // nop, as APFv6 would '<<= R1'
return append(new Instruction(Opcodes.SH).addSigned(val));
}
/**
* Add an instruction to the end of the program to shift right register R0 by {@code value}
* bits.
*/
// TODO: consider whether should change the argument type to byte
public final Type addRightShift(int val) {
return addLeftShift(-val);
}
// R0 op= R1, where op should be one of Opcodes.{ADD,MUL,DIV,AND,OR,SH}
abstract void addR0ArithR1(Opcodes opcode);
/**
* Add an instruction to the end of the program to add register R1 to register R0.
*/
public final Type addAddR1ToR0() {
addR0ArithR1(Opcodes.ADD); // R0 += R1
return self();
}
/**
* Add an instruction to the end of the program to multiply register R0 by register R1.
*/
public final Type addMulR0ByR1() {
addR0ArithR1(Opcodes.MUL); // R0 *= R1
return self();
}
/**
* Add an instruction to the end of the program to divide register R0 by register R1.
*/
public final Type addDivR0ByR1() {
addR0ArithR1(Opcodes.DIV); // R0 /= R1
return self();
}
/**
* Add an instruction to the end of the program to logically and register R0 with register R1
* and store the result back into register R0.
*/
public final Type addAndR0WithR1() {
addR0ArithR1(Opcodes.AND); // R0 &= R1
return self();
}
/**
* Add an instruction to the end of the program to logically or register R0 with register R1
* and store the result back into register R0.
*/
public final Type addOrR0WithR1() {
addR0ArithR1(Opcodes.OR); // R0 |= R1
return self();
}
/**
* Add an instruction to the end of the program to shift register R0 left by the value in
* register R1.
*/
public final Type addLeftShiftR0ByR1() {
addR0ArithR1(Opcodes.SH); // R0 <<= R1
return self();
}
/**
* Add an instruction to the end of the program to move {@code value} into {@code register}.
*/
public final Type addLoadImmediate(Register register, int value) {
return append(new Instruction(Opcodes.LI, register).addSigned(value));
}
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value equals {@code value}.
*/
public final Type addJumpIfR0Equals(long val, String tgt) {
return append(new Instruction(Opcodes.JEQ).addTwosCompUnsigned(val).setTargetLabel(tgt));
}
/**
* Add instructions to the end of the program to increase counter and drop packet if R0 equals
* {@code val}
* WARNING: may modify R1
*/
public abstract Type addCountAndDropIfR0Equals(long val, ApfCounterTracker.Counter cnt)
throws IllegalInstructionException;
/**
* Add instructions to the end of the program to increase counter and pass packet if R0 equals
* {@code val}
* WARNING: may modify R1
*/
public abstract Type addCountAndPassIfR0Equals(long val, ApfCounterTracker.Counter cnt)
throws IllegalInstructionException;
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value does not equal {@code value}.
*/
public final Type addJumpIfR0NotEquals(long val, String tgt) {
return append(new Instruction(Opcodes.JNE).addTwosCompUnsigned(val).setTargetLabel(tgt));
}
/**
* Add instructions to the end of the program to increase counter and drop packet if R0 not
* equals {@code val}
* WARNING: may modify R1
*/
public abstract Type addCountAndDropIfR0NotEquals(long val, ApfCounterTracker.Counter cnt)
throws IllegalInstructionException;
/**
* Add instructions to the end of the program to increase counter and pass packet if R0 not
* equals {@code val}
* WARNING: may modify R1
*/
public abstract Type addCountAndPassIfR0NotEquals(long val, ApfCounterTracker.Counter cnt)
throws IllegalInstructionException;
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value is greater than {@code value}.
*/
public final Type addJumpIfR0GreaterThan(long val, String tgt) {
return append(new Instruction(Opcodes.JGT).addUnsigned(val).setTargetLabel(tgt));
}
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value is less than {@code value}.
*/
public final Type addJumpIfR0LessThan(long val, String tgt) {
return append(new Instruction(Opcodes.JLT).addUnsigned(val).setTargetLabel(tgt));
}
/**
* Add instructions to the end of the program to increase counter and drop packet if R0 less
* than {@code val}
* WARNING: may modify R1
*/
public abstract Type addCountAndDropIfR0LessThan(long val, ApfCounterTracker.Counter cnt)
throws IllegalInstructionException;
/**
* Add instructions to the end of the program to increase counter and pass packet if R0 less
* than {@code val}
* WARNING: may modify R1
*/
public abstract Type addCountAndPassIfR0LessThan(long val, ApfCounterTracker.Counter cnt)
throws IllegalInstructionException;
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value has any bits set that are also set in {@code value}.
*/
public final Type addJumpIfR0AnyBitsSet(long val, String tgt) {
return append(new Instruction(Opcodes.JSET).addTwosCompUnsigned(val).setTargetLabel(tgt));
}
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value equals register R1's value.
*/
public final Type addJumpIfR0EqualsR1(String tgt) {
return append(new Instruction(Opcodes.JEQ, R1).setTargetLabel(tgt));
}
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value does not equal register R1's value.
*/
public final Type addJumpIfR0NotEqualsR1(String tgt) {
return append(new Instruction(Opcodes.JNE, R1).setTargetLabel(tgt));
}
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value is greater than register R1's value.
*/
public final Type addJumpIfR0GreaterThanR1(String tgt) {
return append(new Instruction(Opcodes.JGT, R1).setTargetLabel(tgt));
}
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value is less than register R1's value.
*/
public final Type addJumpIfR0LessThanR1(String target) {
return append(new Instruction(Opcodes.JLT, R1).setTargetLabel(target));
}
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value has any bits set that are also set in R1's value.
*/
public final Type addJumpIfR0AnyBitsSetR1(String tgt) {
return append(new Instruction(Opcodes.JSET, R1).setTargetLabel(tgt));
}
void validateBytes(byte[] bytes) {
Objects.requireNonNull(bytes);
if (bytes.length > 2047) {
throw new IllegalArgumentException(
"bytes array size must be in less than 2048, current size: " + bytes.length);
}
}
/**
* Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
* packet at an offset specified by register0 don't match {@code bytes}.
* R=0 means check for not equal.
*/
public final Type addJumpIfBytesAtR0NotEqual(@NonNull byte[] bytes, String tgt) {
validateBytes(bytes);
return append(new Instruction(Opcodes.JBSMATCH).addUnsigned(
bytes.length).setTargetLabel(tgt).setBytesImm(bytes));
}
/**
* Add instructions to the end of the program to increase counter and drop packet if the
* bytes of the packet at an offset specified by register0 don't match {@code bytes}.
* WARNING: may modify R1
*/
public abstract Type addCountAndDropIfBytesAtR0NotEqual(byte[] bytes,
ApfCounterTracker.Counter cnt) throws IllegalInstructionException;
/**
* Add instructions to the end of the program to increase counter and pass packet if the
* bytes of the packet at an offset specified by register0 don't match {@code bytes}.
* WARNING: may modify R1
*/
public abstract Type addCountAndPassIfBytesAtR0NotEqual(byte[] bytes,
ApfCounterTracker.Counter cnt) throws IllegalInstructionException;
/**
* Add an instruction to the end of the program to load memory slot {@code slot} into
* {@code register}.
*/
public final Type addLoadFromMemory(Register r, MemorySlot slot)
throws IllegalInstructionException {
return append(new BaseApfGenerator.Instruction(ExtendedOpcodes.LDM, slot.value, r));
}
/**
* Add an instruction to the end of the program to store {@code register} into memory slot
* {@code slot}.
*/
public final Type addStoreToMemory(MemorySlot slot, Register r)
throws IllegalInstructionException {
return append(new Instruction(ExtendedOpcodes.STM, slot.value, r));
}
/**
* Add an instruction to the end of the program to logically not {@code register}.
*/
public final Type addNot(Register r) {
return append(new Instruction(ExtendedOpcodes.NOT, r));
}
/**
* Add an instruction to the end of the program to negate {@code register}.
*/
public final Type addNeg(Register r) {
return append(new Instruction(ExtendedOpcodes.NEG, r));
}
/**
* Add an instruction to swap the values in register R0 and register R1.
*/
public final Type addSwap() {
return append(new Instruction(ExtendedOpcodes.SWAP));
}
/**
* Add an instruction to the end of the program to move the value into
* {@code register} from the other register.
*/
public final Type addMove(Register r) {
return append(new Instruction(ExtendedOpcodes.MOVE, r));
}
/**
* Add an instruction to the end of the program to let the program immediately return PASS.
*/
public final Type addPass() {
// PASS requires using Rbit0 because it shares opcode with DROP
return append(new Instruction(Opcodes.PASSDROP, Rbit0));
}
/**
* Abstract method for adding instructions to increment the counter and return PASS.
*/
public abstract Type addCountAndPass(ApfCounterTracker.Counter counter);
/**
* Abstract method for adding instructions to increment the counter and return DROP.
*/
public abstract Type addCountAndDrop(ApfCounterTracker.Counter counter);
/**
* Add an instruction to the end of the program to load 32 bits from the data memory into
* {@code register}.
* In APFv2, it is a noop.
* WARNING: clobbers the *other* register.
*/
public abstract Type addLoadCounter(Register register, ApfCounterTracker.Counter counter)
throws IllegalInstructionException;
/**
* Add an instruction to the end of the program to store 32 bits from {@code register} into the
* data memory.
* In APFv2, it is a noop.
* WARNING: clobbers the *other* register.
*/
public abstract Type addStoreCounter(ApfCounterTracker.Counter counter, Register register)
throws IllegalInstructionException;
/**
* Add an instruction to the end of the program to increment counter value by {@code val).
* In APFv2, it is a noop.
* WARNING: clobbers both registers.
*/
public final Type addIncrementCounter(ApfCounterTracker.Counter counter, int val)
throws IllegalInstructionException {
if (mVersion < 4) return self();
return addLoadCounter(R0, counter).addAdd(val).addStoreCounter(counter, R0);
}
/**
* Add an instruction to the end of the program to increment counter value by one.
* In APFv2, it is a noop.
* WARNING: clobbers both registers.
*/
public final Type addIncrementCounter(ApfCounterTracker.Counter counter)
throws IllegalInstructionException {
return addIncrementCounter(counter, 1);
}
/**
* The abstract method to generate count trampoline instructions.
* @return
* @throws IllegalInstructionException
*/
public abstract Type addCountTrampoline() throws IllegalInstructionException;
}