xref: /aosp_15_r20/external/executorch/runtime/executor/program.cpp (revision 523fa7a60841cd1ecfb9cc4201f1ca8b03ed023a)
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