// SPDX-License-Identifier: Apache-2.0 // Real native adapter for offset.offset_executive_p2 (draft PR #201). // Upstream pinned c9c5979: source/SMLib/{inc,src}/SmOffsetExecutive.{h,cpp}, // source/SM_API/src/SmApiBrep.cpp. Calls all six functions owned by units.json. // Protocol/DTOs: crates/solidcore-ffi/src/units/offset_offset_executive_p2.rs. // Deviations: bounded analytic fixtures, traversal IDs and sampled observations; // no native implementation/helper is included or reimplemented. SC_OK is zero, // native SM_SUCCESS is 1000. [-1,status] is ONLY a fixture-construction rejection. // // Ownership: observe actual per-case B-rep construction/destruction notifications. // Each distinct live allocation is reclaimed once, including private native // working B-reps abandoned on early errors. The executive is destroyed FIRST; // its m_pOffsetBrep remains exclusively executive-owned until then. The static // shell entrypoint uses the explicit transfer-reporting overload. Source lifetime // is independently observed, so retained inputs, aliases and consumed inputs // cannot produce duplicate deletes or stale source snapshots. No address escapes. // The upstream global API context is kept alive with its normal configuration; // our scoped callback and doing-Boolean guard are restored before returning. // The shared native-call guard excludes every other shim until that restoration // and all native destruction complete (including exceptions and buffer retries). #include "sc_shim.h" #include "StdAfx.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr int64_t max_selections = 1024; constexpr int64_t max_input = 4096; constexpr size_t max_output = 262144; constexpr size_t no_allocation = static_cast(-1); struct Argument {}; struct Rejected { SmStatus status; }; void finite(double v) { if (!std::isfinite(v)) throw Argument{}; } struct Reader { const double* data; int64_t size; int64_t pos = 0; double number() { if (pos >= size) throw Argument{}; double v = data[pos++]; finite(v); return v; } int64_t integer(int64_t lo, int64_t hi) { double v = number(); if (v < (double)lo || v > (double)hi || std::floor(v) != v) throw Argument{}; return (int64_t)v; } SmBoolean flag() { return integer(0, 1) ? TRUE : FALSE; } }; void interval(double a, double b, bool strict) { if (a > b || (strict && a == b)) throw Argument{}; finite(b-a); } struct Spec { int kind = 0; std::array v{}; int64_t faces() const { return kind == 3 ? 1 : kind == 4 ? 6 : 0; } }; Spec read_spec(Reader& rd) { Spec s; s.kind = (int)rd.integer(0, 4); int n = s.kind == 2 ? 10 : s.kind == 3 ? 15 : s.kind == 4 ? 6 : 0; for (int i=0; i v[7]) throw Argument{}; double squared = 0; for (int i=0; i<3; ++i) squared += (v[i+3]-v[i])*(v[i+3]-v[i]); finite(squared); } else if (s.kind == 3) { interval(v[11], v[13], true); interval(v[12], v[14], true); for (int begin : {3, 6}) { double squared = 0; for (int i=begin; i selections; bool member() const { return operation == 2 || operation == 3; } bool can_extend() const { // Called only after the whole DTO is parsed and every index validated. // Member NER and both API null checks precede extension arithmetic; // SmApiShellBrepFull also rejects foreign/null selectors before copying. // Use DTO origins, never traversal-index equality, to identify that path. if (source.kind == 0) return false; if (operation == 4) for (const auto& selection : selections) if (selection.origin != 0) return false; // Keep the conservative guard for every other non-null fixture, // including empty/wire B-reps; no additional unreachability is assumed. return operation == 3 || extend != FALSE; } }; Program read_program(Reader& rd) { Program p; p.operation = (int)rd.integer(0, 5); p.tolerance = rd.number(); p.distance = rd.number(); p.member_distance = rd.number(); p.member_tol = rd.number(); p.member_operation = (SmOffsetOperationType)rd.integer(0, 4); p.inset = rd.flag(); p.extend = rd.flag(); p.merge = rd.flag(); p.solid = rd.flag(); p.skip = rd.flag(); if (p.member() && p.member_distance < 0.0) throw Argument{}; p.source = read_spec(rd); p.foreign = read_spec(rd); if (p.source.kind == 0 && (p.operation == 0 || p.operation == 1)) throw Argument{}; int64_t count = rd.integer(0, max_selections); // Parse everything before allocating native geometry. No input-sized reserve. for (int64_t i=0; i= (origin == 0 ? p.source.faces() : p.foreign.faces())) throw Argument{}; p.selections.push_back({origin, index}); } if (rd.pos != rd.size) throw Argument{}; // All scalars remain finite and all selectors remain bounds-checked, even // for proven early returns. Only the unused fivefold product is exempted. if (p.can_extend()) finite((p.member() ? p.member_distance : std::fabs(p.distance))*5.0); return p; } // SmBrep emits construction only after its topology lists are initialized, and // destruction before deleting those lists (SmBrep.cpp:576,682,707). This observer // never changes topology. It catches otherwise inaccessible working allocations // on native early returns as well as the public result. It is scoped to this // single synchronous call; system callbacks and the existing user callback stay // intact. Tokens, not pointer equality, distinguish address reuse after deletion. class Allocations : public SmNotifyCallback { struct Entry { SmBrep* ptr; bool live; }; SmContext& context; SmNotifyCallback* previous; std::vector entries; public: explicit Allocations(SmContext& ctx) : context(ctx), previous(ctx.GetUserNotifyCallback()) { entries.reserve(64); // fixed adapter bookkeeping, not an input-declared size context.SetUserNotifyCallback(this); } void finish() { // All executive instances must already have been destroyed. Continue // reclaiming distinct allocations even if a native destructor throws; // normal-path cleanup reports that as SC_ERR_EXCEPTION at the boundary. bool failed = false; for (size_t i=entries.size(); i>0; --i) { if (entries[i-1].live) { SmBrep* ptr = entries[i-1].ptr; entries[i-1].live = false; try { delete ptr; } catch (...) { failed = true; } } } context.SetUserNotifyCallback(previous); if (failed) throw std::runtime_error("native B-rep destruction threw"); } ~Allocations() override { // Never throw a second exception while unwinding a native operation. try { finish(); } catch (...) { context.SetUserNotifyCallback(previous); } } SmStatus Execute(SmNotifyOperation action, SmObject* object, SmObject* a, SmObject* b, SmObject* c) override { if (action == SM_NO_CONSTRUCTION && object && object->IsKindOf(SmBrep_TYPE)) entries.push_back({static_cast(object), true}); else if (action == SM_NO_DESTRUCTION) { for (auto& entry : entries) if (entry.live && entry.ptr == object) entry.live = false; } return previous ? previous->Execute(action, object, a, b, c) : SM_SUCCESS; } size_t token(const SmBrep* ptr) const { if (!ptr) return no_allocation; for (size_t i=entries.size(); i>0; --i) if (entries[i-1].live && entries[i-1].ptr == ptr) return i-1; throw std::runtime_error("unobserved native B-rep allocation"); } bool live(size_t token) const { return token != no_allocation && entries.at(token).live; } }; struct BooleanRestore { SmContext& context; SmBoolean previous; explicit BooleanRestore(SmContext& ctx) : context(ctx), previous(ctx.GetDoingBoolean()) {} ~BooleanRestore() { context.GetDoingBooleanRef() = previous; } }; void constructed(SmStatus status) { if (status != SM_SUCCESS) throw Rejected{status}; } template struct PendingGeometry { T* ptr; ~PendingGeometry() { if (ptr && !ptr->GetOwner()) delete ptr; } }; SmPoint3d point(const std::array& v, size_t i) { return SmPoint3d(v[i], v[i+1], v[i+2]); } void representable_topology(SmBrep* brep, ULONG face_count, ULONG loop_count, ULONG edge_count, ULONG edgeuse_count, ULONG vertex_count) { SmTArray faces; SmTArray loops; SmTArray edges; SmTArray edgeuses; SmTArray vertices; brep->GetFaces(faces); brep->GetLoops(loops); brep->GetEdges(edges); brep->GetEdgeuses(edgeuses); brep->GetVertices(vertices); // Only DTO-projected counts are required; native shell/region scaffolding // intentionally differs. The allocation observer owns rejection cleanup. if (faces.GetSize() != face_count || loops.GetSize() != loop_count || edges.GetSize() != edge_count || edgeuses.GetSize() != edgeuse_count || vertices.GetSize() != vertex_count) throw Rejected{SM_ERR}; } SmBrep* build(const Spec& s, SmContext& ctx, double tolerance) { if (!s.kind) return nullptr; const auto& v = s.v; SmBrep* result = nullptr; if (s.kind == 4) { constructed(SmApiCreateBox(point(v, 0), v[3], v[4], v[5], result)); if (!result) throw std::runtime_error("successful box constructor returned null"); // CreateBox ignores CreateFaceFromSurface statuses (SmPrimitiveCreation.cpp: // 294-344), so SM_SUCCESS alone does not establish a constructed box. SmTArray faces; SmTArray edges; SmTArray vertices; result->GetFaces(faces); result->GetEdges(edges); result->GetVertices(vertices); if (faces.GetSize() != 6 || edges.GetSize() != 12 || vertices.GetSize() != 8) throw Rejected{SM_ERR}; result->SetTolerance(tolerance, FALSE); return result; } result = new (ctx) SmBrep(); result->SetTolerance(tolerance, FALSE); if (s.kind == 2) { SmExtent1d domain(v[6], v[7]), trim(v[8], v[9]); SmLine* line = nullptr; SmStatus status = SmLine::CreateLineSegment(ctx, 3, point(v, 0), point(v, 3), line, &domain); PendingGeometry pending{line}; constructed(status); if (!line) throw std::runtime_error("successful line constructor returned null"); SmPoint3d a(0,0,0), b(0,0,0); constructed(line->EvaluatePoint(trim.GetMin(), a)); constructed(line->EvaluatePoint(trim.GetMax(), b)); SmShell* shell = nullptr; SmVertex *first = nullptr, *second = nullptr; constructed(result->MakeShellVertex(nullptr, a, shell, first)); constructed(result->MakeShellVertex(nullptr, b, shell, second)); SmEdge* edge = nullptr; // MakeWireEdge may attach the curve before returning an error. The // pending guard only deletes it if it still has no native topology owner. constructed(result->MakeWireEdge(nullptr, first, second, line, trim, SM_OT_SAME, edge)); representable_topology(result, 0, 0, 1, 2, 2); if (!edge) throw std::runtime_error("successful wire constructor returned null"); } else if (s.kind == 3) { SmExtent2d domain(SmPoint2d(v[11],v[12]), SmPoint2d(v[13],v[14])); PendingGeometry pending{ new (ctx) SmPlane(point(v,0), point(v,3), point(v,6), SmVector2d(v[9],v[10]), domain, &ctx) }; SmFace* face = nullptr; constructed(result->CreateFaceFromSurface(pending.ptr, domain, face)); // CreateFaceFromSurface may succeed after tolerance-driven boundary // merges/removal. Reject unrepresentable topology before any selectors // or executive/API calls, without imposing a size or tolerance cutoff. representable_topology(result, 1, 1, 4, 8, 4); if (!face) throw std::runtime_error("successful sheet constructor returned null"); } return result; } // Same safety preflight as p1: public native extraction/normalization checks, // not a replacement for extension. NL_CrvAdv's zero-tangent fallback reads a // third control point even when its isocurve has only two. SmPlane derives from // SmBSplineSurface, so real analytic sheets and box faces require this check too. void guard_two_point_isocurves(SmSurface* base) { SmBSplineSurface* spline = SM_CAST_PTR(SmBSplineSurface, base); if (!spline) return; gw_SURFACE* surface = spline->GetOrCreateGwNurbPointer(); if (!surface) throw Argument{}; for (int direction=0; direction<2; ++direction) { if ((direction == 0 ? surface->net->n : surface->net->m) != 1) continue; gw_KNOTVECTOR* knots = direction == 0 ? surface->knv : surface->knu; for (double fraction : {0.07, 0.19, 0.45, 0.73, 0.92}) { double parameter = (1.0-fraction)*knots->U[0] + fraction*knots->U[knots->m]; std::unique_ptr curve( sm_AllocateNurbCurve(1, 1, 3), &sm_FreeNurbCurve); if (!curve) throw std::bad_alloc(); if (N_SrfExtractIsoCrv(surface, parameter, direction == 0 ? NL_UDIR : NL_VDIR, curve.get(), nullptr) != 0) continue; NL_POINT a, b; NL_VECTOR tangent, unit; NL_REAL magnitude = 0; N_CPtToPtEuclid(curve->pol->Pw[0], &a); N_CPtToPtEuclid(curve->pol->Pw[1], &b); N_VectorDir(a, b, &tangent); if (N_VectorNormalize(tangent, &unit, &magnitude) != 0) throw Argument{}; } } } // Only access to protected flags for observation. No behavior override. class Executive : public SmOffsetExecutive { public: Executive(SmContext& ctx, SmOffsetOperationType op, SmOffsetGeometryCreation& geometry, SmBrep* source) : SmOffsetExecutive(ctx, op, geometry, source) {} bool merge() const { return m_bMergeResults != FALSE; } bool solid() const { return m_bCreateOffsetSolid != FALSE; } }; template double id(const SmTArray& traversal, const T* ptr) { if (!ptr) return -1; for (ULONG i=0; i& out, const SmPoint3d& p) { out.insert(out.end(), {p.x,p.y,p.z}); } void put_domain(std::vector& out, const SmExtent2d& d) { out.insert(out.end(), {d.GetMin().x,d.GetMin().y,d.GetMax().x,d.GetMax().y}); } int surface_kind(SmSurface* surface) { return !surface ? 0 : surface->IsKindOf(SmPlane_TYPE) ? 1 : surface->IsKindOf(SmBSplineSurface_TYPE) ? 2 : 3; } int curve_kind(SmCurve* curve) { return !curve ? 0 : curve->IsKindOf(SmLine_TYPE) ? 1 : curve->IsKindOf(SmBSplineCurve_TYPE) ? 2 : 3; } struct Block { bool present = false; std::vector integers, values; }; template void put_ids(std::vector& out, const SmTArray& list, const SmTArray& traversal) { out.push_back((double)list.GetSize()); for (ULONG i=0; i regions; SmTArray shells; SmTArray faces; SmTArray loops; SmTArray edges; SmTArray edgeuses; SmTArray vertices; brep->GetRegions(regions); brep->GetShells(shells); brep->GetFaces(faces); brep->GetLoops(loops); brep->GetEdges(edges); brep->GetEdgeuses(edgeuses); brep->GetVertices(vertices); size_t total = (size_t)regions.GetSize() + shells.GetSize() + faces.GetSize() + loops.GetSize() + edges.GetSize() + edgeuses.GetSize() + vertices.GetSize(); if (total > max_output/64) throw std::length_error("native snapshot topology budget"); ints.insert(ints.end(), { (double)regions.GetSize(), (double)shells.GetSize(), (double)faces.GetSize(), (double)loops.GetSize(), (double)edges.GetSize(), (double)edgeuses.GetSize(), (double)vertices.GetSize(), id(regions,brep->GetInfiniteRegion()), brep->m_bEditingEnabled ? 1.0 : 0.0, brep->m_bValidateOnly ? 1.0 : 0.0 }); vals.push_back((double)brep->GetTolerance()); for (ULONG i=0; iIsVoid() ? 1.0 : 0.0}); for (ULONG i=0; iGetRegion())}); for (ULONG i=0; iGetSurface(); SmFaceuse* up = face->GetUpwardFaceuse(); SmFaceuse* mate = up ? up->GetMate() : nullptr; ints.insert(ints.end(), {(double)i, (double)surface_kind(surface), id(shells,up ? up->GetShell() : nullptr), id(shells,mate ? mate->GetShell() : nullptr), up ? (double)up->GetOrientation() : -1.0, mate ? (double)mate->GetOrientation() : -1.0}); SmTArray face_loops; face->GetLoops(face_loops); put_ids(ints,face_loops,loops); vals.push_back((double)face->GetTolerance()); SmExtent2d domain = face->GetUVDomain(); put_domain(vals,domain); if (surface) put_domain(vals,surface->GetNaturalUVDomain()); else vals.insert(vals.end(),4,0.0); SmPoint2d low = domain.GetMin(), high = domain.GetMax(); for (double fraction : {0.0,0.5,1.0}) { SmPoint2d uv((1.0-fraction)*low.x + fraction*high.x, (1.0-fraction)*low.y + fraction*high.y); SmPoint3d p(0,0,0); SmVector3d normal(0,0,0); // Nonfinite returned domains are observable, but must not become // unchecked NLib span indices. -1 means sampling was not attempted. SmStatus ps = (SmStatus)-1, ns = (SmStatus)-1; if (surface && std::isfinite(uv.x) && std::isfinite(uv.y)) { ps = surface->EvaluatePoint(uv,p); ns = surface->EvaluateNormal(uv,TRUE,TRUE,normal); } ints.insert(ints.end(), {(double)ps,(double)ns}); put_point(vals,p); put_point(vals,normal); } } for (ULONG i=0; iGetFace())}); SmTArray loop_edgeuses; loops[i]->GetEdgeuses(loop_edgeuses); put_ids(ints,loop_edgeuses,edgeuses); } for (ULONG i=0; iGetCurve(); ints.insert(ints.end(), {(double)i,id(vertices,edge->GetStartVertex()), id(vertices,edge->GetEndVertex()),(double)curve_kind(curve)}); SmTArray uses; edge->GetEdgeuses(uses); put_ids(ints,uses,edgeuses); SmExtent1d trim = edge->GetInterval(); vals.insert(vals.end(), {(double)edge->GetTolerance(),trim.GetMin(),trim.GetMax()}); if (curve) { SmExtent1d domain = curve->GetNaturalInterval(); vals.insert(vals.end(), {domain.GetMin(),domain.GetMax()}); } else vals.insert(vals.end(),2,0.0); for (double t : {trim.GetMin(),trim.GetMax()}) { SmPoint3d p(0,0,0); SmStatus status = (SmStatus)-1; if (curve && std::isfinite(t)) status = curve->EvaluatePoint(t,p); ints.push_back((double)status); put_point(vals,p); } } for (ULONG i=0; iGetEdge()),id(faces,eu->GetFace()), id(shells,eu->GetShell()),id(edgeuses,eu->GetMate()),id(edgeuses,eu->GetRadial()), (double)eu->GetOrientation()}); } for (ULONG i=0; iGetPoint()); vals.push_back((double)vertices[i]->GetTolerance()); } return block; } void put_block(std::vector& out, const Block& block) { out.push_back(block.present ? 1.0 : 0.0); if (!block.present) return; if (block.integers.size() + block.values.size() + out.size() + 2 > max_output) throw std::length_error("native snapshot output budget"); out.push_back((double)block.integers.size()); out.insert(out.end(),block.integers.begin(),block.integers.end()); out.push_back((double)block.values.size()); out.insert(out.end(),block.values.begin(),block.values.end()); } std::vector observe_selectors(const SmTArray& selectors, SmBrep* source, SmBrep* foreign, const std::vector* before = nullptr) { SmTArray sources, foreigners; if (source) source->GetFaces(sources); if (foreign) foreign->GetFaces(foreigners); std::vector result; for (ULONG i=0; iat((size_t)i*2) == 0.0) { result.insert(result.end(), {3.0,before->at((size_t)i*2+1)}); continue; // Do not even compare the consumed source face pointer. } SmFace* face = selectors[i]; double src = id(sources,face), other = id(foreigners,face); if (!face) result.insert(result.end(), {2.0,-1.0}); else if (src >= 0) result.insert(result.end(), {0.0,src}); else if (other >= 0) result.insert(result.end(), {1.0,other}); else result.insert(result.end(), {4.0,-1.0}); } return result; } void run(const Program& p, std::vector& out) { SmContext& ctx = *SmApiGetOrCreateContext(); BooleanRestore restore(ctx); // outlives all per-case B-rep destruction Allocations allocations(ctx); SmBrep* source = build(p.source,ctx,p.tolerance); SmBrep* foreign = build(p.foreign,ctx,p.tolerance); size_t source_token = allocations.token(source), foreign_token = allocations.token(foreign); SmTArray source_faces, foreign_faces, selectors; SmTArray api_selectors; if (source) source->GetFaces(source_faces); if (foreign) foreign->GetFaces(foreign_faces); for (const auto& selection : p.selections) { SmFace* face = nullptr; if (selection.origin != 2) { const auto& list = selection.origin == 0 ? source_faces : foreign_faces; // Expected construction counts are not permission to index an // unexpectedly different native topology. This is a layout error, // never a fabricated constructor rejection or native operation status. if (selection.index < 0 || (ULONG)selection.index >= list.GetSize()) throw std::runtime_error("native fixture face traversal differs from DTO"); face = list[(ULONG)selection.index]; } selectors.Add(face); api_selectors.Add(face); } // Do not cross API selection validation merely to perform a geometry safety // preflight: invalid API selectors must reach the actual early API return. if (source && p.can_extend()) { for (ULONG i=0; iGetSurface()); } Block source_before = snapshot(source), foreign_before = snapshot(foreign); auto selected_before = observe_selectors(selectors,source,foreign); SmOffsetGeometryCreation geometry(p.member_distance,p.member_tol); std::unique_ptr executive; if (p.member()) { executive.reset(new Executive(ctx,p.member_operation,geometry,source)); executive->SetInset(p.inset); executive->SetExtendConvexEdges(p.extend); executive->SetMergeResults(p.merge); executive->SetCreateOffsetSolid(p.solid); executive->SetShellFaces(selectors); } SmBrep* result = nullptr; SmBoolean transferred = FALSE; SmBoolean boolean_before = ctx.GetDoingBoolean(); SmStatus status = SM_ERR; switch (p.operation) { case 0: status = SmOffsetExecutive::OffsetBrep(ctx,source,p.distance,p.extend,p.merge,result); break; case 1: status = SmOffsetExecutive::ShellBrep(ctx,source,p.distance,p.extend,p.merge,p.solid, selectors,result,transferred); break; case 2: status = executive->DoSolidOffset(result,p.skip); break; case 3: status = executive->DoExtendedOffset(result); break; case 4: status = SmApiShellBrepFull(source,p.distance,p.extend,p.merge,p.solid,api_selectors,result); break; case 5: status = SmApiOffsetBrepFull(source,p.distance,p.extend,p.merge,result); break; } SmBoolean boolean_after = ctx.GetDoingBoolean(); bool source_live = allocations.live(source_token), foreign_live = allocations.live(foreign_token); // Verify a non-null result belongs to a live observed allocation BEFORE any // dereference. Native errors remain built-case statuses even with a result. allocations.token(result); SmBrep* offset = executive ? executive->GetOffsetBrep() : nullptr; allocations.token(offset); int lifetime = !source ? 0 : !source_live ? 3 : result == source ? 2 : 1; int origin = !result ? 0 : source_live && result == source ? 2 : result == offset ? 3 : 1; // The explicit transfer report controls ownership interpretation, while the // observer covers actual deletion and also cleans allocations on exceptions. if (p.operation == 1 && transferred && source_live && result != source) throw std::runtime_error("native shell ownership report contradicts source lifetime"); out.insert(out.end(), {0,1,(double)p.operation,(double)status,transferred ? 1.0 : 0.0, (double)lifetime,(double)origin,boolean_before ? 1.0 : 0.0,boolean_after ? 1.0 : 0.0, executive ? 1.0 : 0.0}); if (executive) { out.insert(out.end(), {(double)executive->GetOffsetOperation(), executive->GetInset() ? 1.0 : 0.0,executive->GetExtendConvexEdges() ? 1.0 : 0.0, executive->merge() ? 1.0 : 0.0,executive->solid() ? 1.0 : 0.0, geometry.GetOffsetDistance(),geometry.GetThisApproxTol3d(),executive->GetTangencyTolDegrees()}); } out.push_back((double)selectors.GetSize()); out.insert(out.end(),selected_before.begin(),selected_before.end()); auto selected_after = observe_selectors(selectors,source_live ? source : nullptr, foreign_live ? foreign : nullptr,&selected_before); out.insert(out.end(),selected_after.begin(),selected_after.end()); put_block(out,source_before); put_block(out,snapshot(source_live ? source : nullptr)); put_block(out,foreign_before); put_block(out,snapshot(foreign_live ? foreign : nullptr)); put_block(out,snapshot(result)); put_block(out,snapshot(offset)); // Executive destruction owns offset; then allocation scope reclaims every // remaining distinct B-rep (source/foreign/result/unreturned native working data). executive.reset(); allocations.finish(); } } // namespace SC_EXPORT int32_t sc_offset_offset_executive_p2_eval( const double* input, int64_t input_len, double* out, int64_t cap, int64_t* out_len) { if (!input || input_len < 0 || input_len > max_input || !out_len || cap < 0 || (cap && !out)) return SC_ERR_ARG; try { SC_NATIVE_LOCK; Reader rd{input,input_len}; Program p = read_program(rd); std::vector result; try { run(p,result); } catch (const Rejected& rejected) { result = {-1,(double)rejected.status}; } if (result.size() > max_output) throw std::length_error("native output budget"); return sc_copy_out(result.data(),(int64_t)result.size(),out,cap,out_len); } catch (const Argument&) { return SC_ERR_ARG; } catch (...) { return SC_ERR_EXCEPTION; } }