aboutsummaryrefslogtreecommitdiff
path: root/src/xnnpack/assembler.h
blob: 014c3b6366c4013c978868b9d837a54cbb88bf0e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// Copyright 2022 Google LLC
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.

#pragma once

#include <xnnpack/allocator.h>

#include <array>
#include <cstdint>

typedef uint8_t byte;

namespace xnnpack {

constexpr size_t kInstructionSizeInBytes = 4;
constexpr size_t kInstructionSizeInBytesLog2 = 2;

enum class Error {
  kNoError,
  kOutOfMemory,
  kInvalidOperand,
  kLabelAlreadyBound,
  kLabelOffsetOutOfBounds,
  kLabelHasTooManyUsers,
  kInvalidLaneIndex,
  kInvalidRegisterListLength,
  kFinalizeCodeMemoryFail,
  kUnimplemented,
};

constexpr size_t max_label_users = 10;
// Label is a target of a branch. You call Assembler::bind to bind a label to an
// actual location in the instruction stream.
//
// ```
// Label l;
// b(kAl, l1); // branch to an unbound label is fine, it will be patched later.
// a.bind(l); // binds label to this location in the instruction stream.
// b(kAl, l1); // branch to an already bound label.
// ```
struct Label {
  // Location of label within Assembler buffer.
  byte* offset = nullptr;
  // A label can only be bound once, binding it again leads to an error.
  bool bound = (offset != nullptr);
  // All users of this label, recorded by their offset in the Assembler buffer.
  std::array<byte*, max_label_users> users{{0}};
  size_t num_users = 0;

  // Records a user (e.g. branch instruction) of this label.
  // Returns true if success, false if number of users exceeds maximum.
  bool add_use(byte* offset) {
    if (num_users >= max_label_users) {
      return false;
    }
    users[num_users++] = offset;
    return true;
  }
};

class AssemblerBase {
 public:
  // Takes an xnn_code_buffer with a pointer to allocated memory. If the buffer
  // already contains content (size != 0), appends to after size (up to capacity).
  explicit AssemblerBase(xnn_code_buffer* buf);

  // Write value into the code buffer and advances cursor_.
  void emit32(uint32_t value);
  // Finish assembly of code, this should be the last function called on an
  // instance of Assembler. Returns a pointer to the start of code region.
  void* finalize();
  // Reset the assembler state (no memory is freed).
  void reset();

  // Get a pointer to the start of code buffer.
  const byte* start() const { return buffer_; }
  const byte* offset() const { return cursor_; }
  template<typename T>
  const T offset() const { return reinterpret_cast<T>(cursor_); }
  // Returns the number of bytes of code actually in the buffer.
  size_t code_size_in_bytes() const { return (cursor_ - buffer_); }
  const Error error() const { return error_; }

 protected:
  // Pointer into code buffer to start writing code.
  byte* buffer_;
  // Pointer to current position in code buffer.
  byte* cursor_;
  // Pointer to out-of-bounds of code buffer.
  byte* top_;
  // Errors encountered while assembling code.
  Error error_ = Error::kNoError;
  // Holds an xnn_code_buffer, will write code to its code pointer, and unmap
  // unused pages on finalizing.
  xnn_code_buffer* xnn_buffer = nullptr;
};

}  // namespace xnnpack