1 /*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9 #include <executorch/runtime/executor/program.h>
10
11 #include <cstddef>
12 #include <cstdint>
13
14 #include <executorch/runtime/core/event_tracer_hooks.h>
15 #include <executorch/runtime/executor/memory_manager.h>
16 #include <executorch/runtime/executor/method.h>
17 #include <executorch/runtime/platform/profiler.h>
18 #include <executorch/schema/extended_header.h>
19 #include <executorch/schema/program_generated.h>
20
21 /*
22 * Program verification can increase code size by ~30k. Targets that need to
23 * save this space can avoid building it by passing
24 * -DET_ENABLE_PROGRAM_VERIFICATION=0 on the compile line.
25 */
26 #ifndef ET_ENABLE_PROGRAM_VERIFICATION
27 #define ET_ENABLE_PROGRAM_VERIFICATION 1
28 #endif
29
30 namespace executorch {
31 namespace runtime {
32
33 namespace {
34
35 /**
36 * Program data must be aligned to this value to properly parse it. Must be a
37 * power of 2. Note that max_align_t is the alignment that malloc() and new
38 * guarantee.
39 */
40 constexpr size_t kMinimumAlignment = alignof(std::max_align_t);
41
IsAligned(const void * data)42 bool IsAligned(const void* data) {
43 uintptr_t addr = reinterpret_cast<uintptr_t>(data);
44 return addr % kMinimumAlignment == 0;
45 }
46
get_execution_plan(const executorch_flatbuffer::Program * program,const char * method_name)47 Result<executorch_flatbuffer::ExecutionPlan*> get_execution_plan(
48 const executorch_flatbuffer::Program* program,
49 const char* method_name) {
50 auto execution_plans = program->execution_plan();
51 for (size_t i = 0; i < execution_plans->size(); i++) {
52 auto plan = execution_plans->GetMutableObject(i);
53 if (std::strcmp(plan->name()->c_str(), method_name) == 0) {
54 return plan;
55 }
56 }
57 ET_LOG(Error, "No method named '%s' in program", method_name);
58 return Error::InvalidArgument;
59 }
60
61 } // namespace
62
load(DataLoader * loader,Program::Verification verification)63 /* static */ Result<Program> Program::load(
64 DataLoader* loader,
65 Program::Verification verification) {
66 EXECUTORCH_SCOPE_PROF("Program::load");
67
68 // See if the program size is in the header.
69 size_t program_size = 0;
70 size_t segment_base_offset = 0;
71 {
72 EXECUTORCH_SCOPE_PROF("Program::check_header");
73 Result<FreeableBuffer> header = loader->load(
74 /*offset=*/0,
75 ExtendedHeader::kNumHeadBytes,
76 DataLoader::SegmentInfo(DataLoader::SegmentInfo::Type::Program));
77 if (!header.ok()) {
78 return header.error();
79 }
80 Result<ExtendedHeader> eh =
81 ExtendedHeader::Parse(header->data(), header->size());
82 if (eh.ok()) {
83 // The header has the program size.
84 program_size = eh->program_size;
85 segment_base_offset = eh->segment_base_offset;
86 } else if (eh.error() == Error::NotFound) {
87 // No header; the program consumes the whole file, and there are no
88 // segments.
89 auto result = loader->size();
90 if (!result.ok()) {
91 return result.error();
92 }
93 program_size = result.get();
94 } else {
95 ET_LOG(Error, "Extended header may be corrupt");
96 return eh.error();
97 }
98 }
99
100 // Load the flatbuffer data as a segment.
101 uint32_t prof_tok = EXECUTORCH_BEGIN_PROF("Program::load_data");
102 Result<FreeableBuffer> program_data = loader->load(
103 /*offset=*/0,
104 program_size,
105 DataLoader::SegmentInfo(DataLoader::SegmentInfo::Type::Program));
106 if (!program_data.ok()) {
107 return program_data.error();
108 }
109 EXECUTORCH_END_PROF(prof_tok);
110
111 // Make sure the magic header matches the expected version.
112 if (!executorch_flatbuffer::ProgramBufferHasIdentifier(
113 program_data->data())) {
114 ET_LOG(
115 Error,
116 "Program identifier '%.4s' != expected '%.4s'",
117 flatbuffers::GetBufferIdentifier(program_data->data()),
118 executorch_flatbuffer::ProgramIdentifier());
119 return Error::InvalidProgram;
120 }
121
122 // Do extra verification if requested.
123 if (verification == Verification::InternalConsistency) {
124 #if ET_ENABLE_PROGRAM_VERIFICATION
125 EXECUTORCH_SCOPE_PROF("Program::verify_internal_consistency");
126 flatbuffers::Verifier verifier(
127 reinterpret_cast<const uint8_t*>(program_data->data()),
128 program_data->size());
129 bool ok = executorch_flatbuffer::VerifyProgramBuffer(verifier);
130 ET_CHECK_OR_RETURN_ERROR(
131 ok,
132 InvalidProgram,
133 "Verification failed; data may be truncated or corrupt");
134 #else
135 ET_LOG(
136 Info, "InternalConsistency verification requested but not available");
137 #endif
138 }
139
140 // The flatbuffer data must start at an aligned address to ensure internal
141 // alignment of flatbuffer fields.
142 ET_CHECK_OR_RETURN_ERROR(
143 IsAligned(program_data->data()),
144 InvalidArgument,
145 "Program data 0x%p must be aligned to %zu",
146 program_data->data(),
147 kMinimumAlignment);
148
149 // Get the pointer to the root flatbuffer table.
150 const executorch_flatbuffer::Program* flatbuffer_program =
151 executorch_flatbuffer::GetProgram(program_data->data());
152
153 // Constant data may live inside the flatbuffer data (constant_buffer) or in a
154 // separate segment (constant_segment). It should not be in both.
155 // Check constant_segment->offsets()->size() > 1, as the offsets list will
156 // always contain a placeholder value 0 for non-const tensors. If this is the
157 // only offset, the constant segment is empty and does not need to be loaded.
158 const auto* constant_segment = flatbuffer_program->constant_segment();
159 if (constant_segment != nullptr && constant_segment->offsets() != nullptr &&
160 constant_segment->offsets()->size() > 1) {
161 // The constant data is inside a separate segment.
162 const auto* constant_buffer = flatbuffer_program->constant_buffer();
163 ET_CHECK_OR_RETURN_ERROR(
164 constant_buffer == nullptr || constant_buffer->size() == 0,
165 InvalidProgram,
166 "constant_buffer contains %u items, "
167 "constant_segment.offsets contains %u items. Only one should be used.",
168 constant_buffer->size(),
169 constant_segment->offsets()->size());
170 const auto* segments = flatbuffer_program->segments();
171 ET_CHECK_OR_RETURN_ERROR(
172 segments != nullptr, InvalidProgram, "No segments in program");
173
174 // Load constant segment.
175 // TODO(T171839323): Add test for segment_index > num available segments.
176 ET_CHECK_OR_RETURN_ERROR(
177 constant_segment->segment_index() < segments->size(),
178 InvalidProgram,
179 "Constant segment index %d invalid for program segments range %d",
180 constant_segment->segment_index(),
181 segments->size());
182
183 const executorch_flatbuffer::DataSegment* data_segment =
184 segments->Get(constant_segment->segment_index());
185 Result<FreeableBuffer> constant_segment_data = loader->load(
186 segment_base_offset + data_segment->offset(),
187 data_segment->size(),
188 DataLoader::SegmentInfo(
189 DataLoader::SegmentInfo::Type::Constant,
190 constant_segment->segment_index()));
191 if (!constant_segment_data.ok()) {
192 return constant_segment_data.error();
193 }
194 // The FreeableBuffer owns the data that flatbuffer_program points into.
195 // Also keep a pointer to the loader so it can load more segments when
196 // necessary.
197 return Program(
198 loader,
199 segment_base_offset,
200 std::move(program_data.get()),
201 flatbuffer_program,
202 std::move(constant_segment_data.get()));
203 } else {
204 // The constant data is stored inside the flatbuffer, so this program does
205 // not contain a separate segment for it.
206 return Program(
207 loader,
208 segment_base_offset,
209 std::move(program_data.get()),
210 flatbuffer_program,
211 /*constant_segment_data=*/FreeableBuffer{});
212 }
213 }
214
num_methods() const215 size_t Program::num_methods() const {
216 auto internal_program =
217 static_cast<const executorch_flatbuffer::Program*>(internal_program_);
218 const auto execution_plan = internal_program->execution_plan();
219 if (execution_plan != nullptr) {
220 return execution_plan->size();
221 } else {
222 return 0;
223 }
224 }
225
get_method_name(size_t plan_index) const226 Result<const char*> Program::get_method_name(size_t plan_index) const {
227 if (plan_index >= this->num_methods()) {
228 return Error::InvalidArgument;
229 }
230 auto internal_program =
231 static_cast<const executorch_flatbuffer::Program*>(internal_program_);
232 // We know that the execution plan exists because num_methods() returned > 0.
233 auto name = internal_program->execution_plan()->Get(plan_index)->name();
234 if (name == nullptr) {
235 return Error::InvalidProgram;
236 }
237 return name->c_str();
238 }
239
load_method(const char * method_name,MemoryManager * memory_manager,EventTracer * event_tracer) const240 Result<Method> Program::load_method(
241 const char* method_name,
242 MemoryManager* memory_manager,
243 EventTracer* event_tracer) const {
244 EXECUTORCH_SCOPE_PROF("Program::load_method");
245 internal::event_tracer_create_event_block(event_tracer, "Default");
246 internal::EventTracerProfileMethodScope event_tracer_scope =
247 internal::EventTracerProfileMethodScope(
248 event_tracer, "Program::load_method");
249 // If we can't create a MethodMeta for the Method, the Method is corrupt;
250 // Method::method_meta() assumes success, so we must fail here.
251 Result<MethodMeta> meta = method_meta(method_name);
252 if (!meta.ok()) {
253 return meta.error();
254 }
255
256 auto plan = get_execution_plan(internal_program_, method_name);
257 if (!plan.ok()) {
258 return plan.error();
259 }
260 return Method::load(plan.get(), this, memory_manager, event_tracer);
261 }
262
method_meta(const char * method_name) const263 Result<MethodMeta> Program::method_meta(const char* method_name) const {
264 auto plan = get_execution_plan(internal_program_, method_name);
265 if (!plan.ok()) {
266 return plan.error();
267 }
268 // Check any fields whose accessors don't return Result<> in case they're
269 // missing or corrupt.
270 ET_CHECK_OR_RETURN_ERROR(
271 plan.get()->name() != nullptr, InvalidProgram, "Missing name field");
272 ET_CHECK_OR_RETURN_ERROR(
273 plan.get()->non_const_buffer_sizes() != nullptr,
274 InvalidProgram,
275 "Missing non_const_buffer_sizes field");
276 ET_CHECK_OR_RETURN_ERROR(
277 plan.get()->inputs() != nullptr, InvalidProgram, "Missing inputs field");
278 ET_CHECK_OR_RETURN_ERROR(
279 plan.get()->outputs() != nullptr,
280 InvalidProgram,
281 "Missing outputs field");
282 return MethodMeta(plan.get());
283 }
284
get_constant_buffer_data(size_t buffer_index,size_t nbytes) const285 Result<const void*> Program::get_constant_buffer_data(
286 size_t buffer_index,
287 size_t nbytes) const {
288 auto internal_program =
289 static_cast<const executorch_flatbuffer::Program*>(internal_program_);
290
291 // Constant data is either in a separate segment (constant_segment_data) and
292 // loaded during Program::load, or stored inside the flatbuffer data
293 // (constant_buffer).
294 if (constant_segment_data_.data() != nullptr) {
295 size_t num_elems = internal_program->constant_segment()->offsets()->size();
296 ET_CHECK_OR_RETURN_ERROR(
297 buffer_index < num_elems,
298 InvalidArgument,
299 "Constant segment buffer index %zu invalid for program constant segment range %zu",
300 buffer_index,
301 num_elems);
302
303 // All constant data is stored in one segment, with each tensor aligned to
304 // @executorch_tensor_alignment. Tensor offsets are stored in the flatbuffer
305 // data in Program.constant_segment.offsets.
306 // The constant data at buffer_index is located at: base address of the
307 // constant segment + offset for tensor at buffer_index.
308 uint64_t offset = static_cast<uint64_t>(
309 (*internal_program->constant_segment()->offsets())[buffer_index]);
310
311 size_t size = constant_segment_data_.size();
312 ET_CHECK_OR_RETURN_ERROR(
313 offset + nbytes <= size,
314 InvalidArgument,
315 "Constant segment offset %" PRIu64
316 " + size_bytes %zu invalid for program constant segment size %zu",
317 offset,
318 nbytes,
319 size);
320
321 // Offset is wrt the beginning of the constant segment.
322 return static_cast<const void*>(
323 static_cast<const unsigned char*>(constant_segment_data_.data()) +
324 offset);
325 } else {
326 // Otherwise, the constant data is stored inside Program.constant_buffer.
327 size_t num_elems = internal_program->constant_buffer()->size();
328 ET_CHECK_OR_RETURN_ERROR(
329 buffer_index < num_elems,
330 InvalidArgument,
331 "Constant buffer index %zu invalid for program constant buffer range %zu",
332 buffer_index,
333 num_elems);
334
335 const auto& constant_buffer = *internal_program->constant_buffer();
336
337 ET_CHECK_OR_RETURN_ERROR(
338 constant_buffer[buffer_index]->storage()->size() <= nbytes,
339 InvalidArgument,
340 "Constant buffer size %u larger than allocated nbytes %zu",
341 constant_buffer[buffer_index]->storage()->size(),
342 nbytes);
343
344 return static_cast<const void*>(
345 constant_buffer[buffer_index]->storage()->data());
346 }
347 }
348
get_output_flattening_encoding(const char * method_name) const349 Result<const char*> Program::get_output_flattening_encoding(
350 const char* method_name) const {
351 auto plan = get_execution_plan(internal_program_, method_name);
352 if (!plan.ok()) {
353 return plan.error();
354 }
355 return plan.get()->container_meta_type()->encoded_out_str()->c_str();
356 }
357
get_backend_delegate_data(size_t index,const void ** out_data,size_t * out_size) const358 Error Program::get_backend_delegate_data(
359 size_t index,
360 const void** out_data,
361 size_t* out_size) const {
362 const auto* data_list =
363 static_cast<const executorch_flatbuffer::Program*>(internal_program_)
364 ->backend_delegate_data();
365 ET_CHECK_OR_RETURN_ERROR(
366 index < data_list->size(),
367 NotFound,
368 "index %zu >= list size %" PRIu32,
369 index,
370 data_list->size());
371 auto data = data_list->Get(index)->data();
372 *out_data = data->data();
373 *out_size = data->size();
374 return Error::Ok;
375 }
376
check_header(const void * data,size_t size)377 /* static */ Program::HeaderStatus Program::check_header(
378 const void* data,
379 size_t size) {
380 if (size < kMinHeadBytes) {
381 return HeaderStatus::ShortData;
382 }
383 if (executorch_flatbuffer::ProgramBufferHasIdentifier(data)) {
384 // The data has the same file_identifier string as the schema.fbs file
385 // that this runtime was built with.
386 return HeaderStatus::CompatibleVersion;
387 }
388 const char* id = flatbuffers::GetBufferIdentifier(data);
389 if (id[0] == 'E' && id[1] == 'T') {
390 // It looks like an executorch file, but not the version we expect.
391 return HeaderStatus::IncompatibleVersion;
392 }
393 return HeaderStatus::NotPresent;
394 }
395
LoadSegment(const DataLoader::SegmentInfo & segment_info) const396 Result<FreeableBuffer> Program::LoadSegment(
397 const DataLoader::SegmentInfo& segment_info) const {
398 EXECUTORCH_SCOPE_PROF("Program::LoadSegment");
399 size_t index = segment_info.segment_index;
400 if (loader_ == nullptr || segment_base_offset_ == 0) {
401 ET_LOG(Error, "No segments in program: requested index %zu", index);
402 return Error::NotFound;
403 }
404 size_t num_segments = internal_program_->segments()->size();
405 if (index >= num_segments) {
406 ET_LOG(
407 Error, "Segment index %zu out of range (>= %zu)", index, num_segments);
408 return Error::NotFound;
409 }
410 const executorch_flatbuffer::DataSegment* segment =
411 internal_program_->segments()->Get(index);
412 // Could fail if offset and size are out of bound for the data, or if this
413 // is reading from a file and fails, or for many other reasons depending on
414 // the implementation of the loader.
415 return loader_->load(
416 segment_base_offset_ + segment->offset(), segment->size(), segment_info);
417 }
418
load_mutable_subsegment_into(size_t mutable_data_segments_index,size_t offset_index,size_t size,void * buffer) const419 Error Program::load_mutable_subsegment_into(
420 size_t mutable_data_segments_index,
421 size_t offset_index,
422 size_t size,
423 void* buffer) const {
424 EXECUTORCH_SCOPE_PROF("Program::load_subsegment_into");
425 // Check that the program has segments.
426 if (loader_ == nullptr || segment_base_offset_ == 0) {
427 ET_LOG(Error, "No segments in program");
428 return Error::NotFound;
429 }
430
431 // Check that the program has mutable data segments.
432 if (internal_program_->mutable_data_segments() == nullptr) {
433 ET_LOG(Error, "No mutable data segments in program");
434 return Error::NotFound;
435 }
436 if (mutable_data_segments_index >=
437 internal_program_->mutable_data_segments()->size()) {
438 ET_LOG(
439 Error,
440 "mutable_data_segments_index %zu out of range >= %" PRIu64,
441 mutable_data_segments_index,
442 (uint64_t)internal_program_->mutable_data_segments()->size());
443 return Error::NotFound;
444 }
445
446 // Grab the mutable data segment info.
447 const auto& segment_offsets = internal_program_->mutable_data_segments()->Get(
448 mutable_data_segments_index);
449
450 // Check that the offset is valid.
451 if (segment_offsets->offsets() == nullptr) {
452 ET_LOG(Error, "No offsets in mutable data segment");
453 return Error::NotFound;
454 }
455 if (offset_index >= segment_offsets->offsets()->size()) {
456 ET_LOG(
457 Error,
458 "offset index %zu out of range >= %" PRIu64,
459 offset_index,
460 (uint64_t)segment_offsets->offsets()->size());
461 return Error::NotFound;
462 }
463
464 // Grab the offset. Note: This offset is relative to the start of the segment,
465 // so we will need to adjust when calling the loader.
466 size_t offset = segment_offsets->offsets()->Get(offset_index);
467
468 // Grab the segment index
469 size_t num_segments = internal_program_->segments()->size();
470 if (segment_offsets->segment_index() >= num_segments) {
471 ET_LOG(
472 Error,
473 "Segment index %u out of range (>= %zu)",
474 segment_offsets->segment_index(),
475 num_segments);
476 return Error::NotFound;
477 }
478
479 // Grab the segment
480 auto segment =
481 internal_program_->segments()->Get(segment_offsets->segment_index());
482
483 // Check size
484 if (offset + size > segment->size()) {
485 ET_LOG(
486 Error,
487 "offset %zu + size %zu out of range > %" PRIu64,
488 offset,
489 size,
490 segment->size());
491 return Error::InvalidArgument;
492 }
493
494 DataLoader::SegmentInfo info = DataLoader::SegmentInfo(
495 DataLoader::SegmentInfo::Type::Mutable,
496 segment_offsets->segment_index(),
497 nullptr);
498
499 // Load the data
500 return loader_->load_into(
501 segment_base_offset_ + segment->offset() + offset, size, info, buffer);
502 }
503
504 } // namespace runtime
505 } // namespace executorch
506