mirror of
https://github.com/pocketpy/pocketpy
synced 2025-10-20 03:20:18 +00:00
...
This commit is contained in:
parent
fdfe20ead1
commit
680a678123
@ -296,7 +296,7 @@ struct C99ReflType final: ReflType{
|
||||
}
|
||||
|
||||
static void _register(VM* vm, PyObject* mod, PyObject* type){
|
||||
vm->bind_constructor<-1>(type, CPP_NOT_IMPLEMENTED());
|
||||
vm->bind_notimplemented_constructor<C99ReflType>(type);
|
||||
|
||||
vm->bind_method<0>(type, "__call__", [](VM* vm, ArgsView args){
|
||||
C99ReflType& self = _CAST(C99ReflType&, args[0]);
|
||||
|
@ -131,7 +131,6 @@ struct Type {
|
||||
|
||||
#define THREAD_LOCAL // thread_local
|
||||
#define CPP_LAMBDA(x) ([](VM* vm, ArgsView args) { return x; })
|
||||
#define CPP_NOT_IMPLEMENTED() ([](VM* vm, ArgsView args) { vm->NotImplementedError(); return vm->None; })
|
||||
|
||||
#ifdef POCKETPY_H
|
||||
#define FATAL_ERROR() throw std::runtime_error( "L" + std::to_string(__LINE__) + " FATAL_ERROR()!");
|
||||
|
185
src/iter.h
185
src/iter.h
@ -1,98 +1,131 @@
|
||||
#pragma once
|
||||
|
||||
#include "ceval.h"
|
||||
#include "cffi.h"
|
||||
#include "common.h"
|
||||
#include "frame.h"
|
||||
|
||||
namespace pkpy{
|
||||
|
||||
class RangeIter final: public BaseIter {
|
||||
struct RangeIter{
|
||||
PY_CLASS(RangeIter, builtins, "_range_iterator")
|
||||
Range r;
|
||||
i64 current;
|
||||
Range r; // copy by value, so we don't need to keep ref
|
||||
public:
|
||||
RangeIter(VM* vm, PyObject* ref) : BaseIter(vm) {
|
||||
this->r = OBJ_GET(Range, ref);
|
||||
this->current = r.start;
|
||||
}
|
||||
RangeIter(Range r) : r(r), current(r.start) {}
|
||||
|
||||
bool _has_next(){
|
||||
return r.step > 0 ? current < r.stop : current > r.stop;
|
||||
}
|
||||
|
||||
PyObject* next(){
|
||||
if(!_has_next()) return vm->StopIteration;
|
||||
current += r.step;
|
||||
return VAR(current-r.step);
|
||||
static void _register(VM* vm, PyObject* mod, PyObject* type){
|
||||
vm->_all_types[OBJ_GET(Type, type)].subclass_enabled = false;
|
||||
vm->bind_notimplemented_constructor<RangeIter>(type);
|
||||
vm->bind__iter__(OBJ_GET(Type, type), [](VM* vm, PyObject* obj){ return obj; });
|
||||
vm->bind__next__(OBJ_GET(Type, type), [](VM* vm, PyObject* obj){
|
||||
RangeIter& self = _CAST(RangeIter&, obj);
|
||||
bool has_next = self.r.step > 0 ? self.current < self.r.stop : self.current > self.r.stop;
|
||||
if(!has_next) return vm->StopIteration;
|
||||
self.current += self.r.step;
|
||||
return VAR(self.current - self.r.step);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class ArrayIter final: public BaseIter {
|
||||
struct ArrayIter{
|
||||
PY_CLASS(ArrayIter, builtins, "_array_iterator")
|
||||
PyObject* ref;
|
||||
T* array;
|
||||
int index;
|
||||
public:
|
||||
ArrayIter(VM* vm, PyObject* ref) : BaseIter(vm), ref(ref) {
|
||||
array = &OBJ_GET(T, ref);
|
||||
index = 0;
|
||||
}
|
||||
PyObject** begin;
|
||||
PyObject** end;
|
||||
PyObject** current;
|
||||
|
||||
PyObject* next() override{
|
||||
if(index >= array->size()) return vm->StopIteration;
|
||||
return array->operator[](index++);
|
||||
ArrayIter(PyObject* ref, PyObject** begin, PyObject** end)
|
||||
: ref(ref), begin(begin), end(end), current(begin) {}
|
||||
|
||||
void _gc_mark() const{ OBJ_MARK(ref); }
|
||||
|
||||
static void _register(VM* vm, PyObject* mod, PyObject* type){
|
||||
vm->_all_types[OBJ_GET(Type, type)].subclass_enabled = false;
|
||||
vm->bind_notimplemented_constructor<ArrayIter>(type);
|
||||
vm->bind__iter__(OBJ_GET(Type, type), [](VM* vm, PyObject* obj){ return obj; });
|
||||
vm->bind__next__(OBJ_GET(Type, type), [](VM* vm, PyObject* obj){
|
||||
ArrayIter& self = _CAST(ArrayIter&, obj);
|
||||
if(self.current == self.end) return vm->StopIteration;
|
||||
return *self.current++;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
struct StringIter{
|
||||
PY_CLASS(StringIter, builtins, "_string_iterator")
|
||||
PyObject* ref;
|
||||
Str* str;
|
||||
int index;
|
||||
|
||||
StringIter(PyObject* ref) : ref(ref), str(&OBJ_GET(Str, ref)), index(0) {}
|
||||
|
||||
void _gc_mark() const{ OBJ_MARK(ref); }
|
||||
|
||||
static void _register(VM* vm, PyObject* mod, PyObject* type){
|
||||
vm->_all_types[OBJ_GET(Type, type)].subclass_enabled = false;
|
||||
vm->bind_notimplemented_constructor<StringIter>(type);
|
||||
vm->bind__iter__(OBJ_GET(Type, type), [](VM* vm, PyObject* obj){ return obj; });
|
||||
vm->bind__next__(OBJ_GET(Type, type), [](VM* vm, PyObject* obj){
|
||||
StringIter& self = _CAST(StringIter&, obj);
|
||||
// TODO: optimize this... operator[] is of O(n) complexity
|
||||
if(self.index == self.str->u8_length()) return vm->StopIteration;
|
||||
return VAR(self.str->u8_getitem(self.index++));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
struct Generator{
|
||||
PY_CLASS(Generator, builtins, "_generator")
|
||||
Frame frame;
|
||||
int state; // 0,1,2
|
||||
List s_backup;
|
||||
|
||||
Generator(Frame&& frame, ArgsView buffer): frame(std::move(frame)), state(0) {
|
||||
for(PyObject* obj: buffer) s_backup.push_back(obj);
|
||||
}
|
||||
|
||||
void _gc_mark() const{
|
||||
OBJ_MARK(ref);
|
||||
frame._gc_mark();
|
||||
for(PyObject* obj: s_backup) OBJ_MARK(obj);
|
||||
}
|
||||
|
||||
PyObject* next(VM* vm){
|
||||
if(state == 2) return vm->StopIteration;
|
||||
// reset frame._sp_base
|
||||
frame._sp_base = frame._s->_sp;
|
||||
frame._locals.a = frame._s->_sp;
|
||||
// restore the context
|
||||
for(PyObject* obj: s_backup) frame._s->push(obj);
|
||||
s_backup.clear();
|
||||
vm->callstack.push(std::move(frame));
|
||||
PyObject* ret = vm->_run_top_frame();
|
||||
if(ret == PY_OP_YIELD){
|
||||
// backup the context
|
||||
frame = std::move(vm->callstack.top());
|
||||
PyObject* ret = frame._s->popx();
|
||||
for(PyObject* obj: frame.stack_view()) s_backup.push_back(obj);
|
||||
vm->_pop_frame();
|
||||
state = 1;
|
||||
if(ret == vm->StopIteration) state = 2;
|
||||
return ret;
|
||||
}else{
|
||||
state = 2;
|
||||
return vm->StopIteration;
|
||||
}
|
||||
}
|
||||
|
||||
static void _register(VM* vm, PyObject* mod, PyObject* type){
|
||||
vm->_all_types[OBJ_GET(Type, type)].subclass_enabled = false;
|
||||
vm->bind_notimplemented_constructor<Generator>(type);
|
||||
vm->bind__iter__(OBJ_GET(Type, type), [](VM* vm, PyObject* obj){ return obj; });
|
||||
vm->bind__next__(OBJ_GET(Type, type), [](VM* vm, PyObject* obj){
|
||||
Generator& self = _CAST(Generator&, obj);
|
||||
return self.next(vm);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class StringIter final: public BaseIter {
|
||||
PyObject* ref;
|
||||
int index;
|
||||
public:
|
||||
StringIter(VM* vm, PyObject* ref) : BaseIter(vm), ref(ref), index(0) {}
|
||||
|
||||
PyObject* next() override{
|
||||
// TODO: optimize this to use iterator
|
||||
// operator[] is O(n) complexity
|
||||
Str* str = &OBJ_GET(Str, ref);
|
||||
if(index == str->u8_length()) return vm->StopIteration;
|
||||
return VAR(str->u8_getitem(index++));
|
||||
}
|
||||
|
||||
void _gc_mark() const{
|
||||
OBJ_MARK(ref);
|
||||
}
|
||||
};
|
||||
|
||||
inline PyObject* Generator::next(){
|
||||
if(state == 2) return vm->StopIteration;
|
||||
// reset frame._sp_base
|
||||
frame._sp_base = frame._s->_sp;
|
||||
frame._locals.a = frame._s->_sp;
|
||||
// restore the context
|
||||
for(PyObject* obj: s_backup) frame._s->push(obj);
|
||||
s_backup.clear();
|
||||
vm->callstack.push(std::move(frame));
|
||||
PyObject* ret = vm->_run_top_frame();
|
||||
if(ret == PY_OP_YIELD){
|
||||
// backup the context
|
||||
frame = std::move(vm->callstack.top());
|
||||
PyObject* ret = frame._s->popx();
|
||||
for(PyObject* obj: frame.stack_view()) s_backup.push_back(obj);
|
||||
vm->_pop_frame();
|
||||
state = 1;
|
||||
if(ret == vm->StopIteration) state = 2;
|
||||
return ret;
|
||||
}else{
|
||||
state = 2;
|
||||
return vm->StopIteration;
|
||||
}
|
||||
}
|
||||
|
||||
inline void Generator::_gc_mark() const{
|
||||
frame._gc_mark();
|
||||
for(PyObject* obj: s_backup) OBJ_MARK(obj);
|
||||
inline PyObject* VM::_py_generator(Frame&& frame, ArgsView buffer){
|
||||
return VAR_T(Generator, std::move(frame), buffer);
|
||||
}
|
||||
|
||||
} // namespace pkpy
|
12
src/linalg.h
12
src/linalg.h
@ -204,12 +204,6 @@ struct Mat3x3{
|
||||
}
|
||||
|
||||
/*************** affine transformations ***************/
|
||||
static Mat3x3 translate(Vec2 v){
|
||||
return Mat3x3(1.0f, 0.0f, v.x,
|
||||
0.0f, 1.0f, v.y,
|
||||
0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
static Mat3x3 rotate(float radian){
|
||||
float cr = cosf(radian);
|
||||
float sr = sinf(radian);
|
||||
@ -218,12 +212,6 @@ struct Mat3x3{
|
||||
0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
static Mat3x3 scale(Vec2 s){
|
||||
return Mat3x3(s.x, 0.0f, 0.0f,
|
||||
0.0f, s.y, 0.0f,
|
||||
0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
static Mat3x3 trs(Vec2 t, float radian, Vec2 s){
|
||||
float cr = cosf(radian);
|
||||
float sr = sinf(radian);
|
||||
|
@ -246,16 +246,13 @@ inline size_t memory_usage(){
|
||||
return pool64.allocated_size() + pool128.allocated_size();
|
||||
}
|
||||
|
||||
#define SP_MALLOC(size) pool128.alloc(size)
|
||||
#define SP_FREE(p) pool128.dealloc(p)
|
||||
|
||||
template <typename T>
|
||||
struct shared_ptr {
|
||||
int* counter;
|
||||
|
||||
T* _t() const noexcept { return (T*)(counter + 1); }
|
||||
void _inc_counter() { if(counter) ++(*counter); }
|
||||
void _dec_counter() { if(counter && --(*counter) == 0) {((T*)(counter + 1))->~T(); SP_FREE(counter);} }
|
||||
void _dec_counter() { if(counter && --(*counter) == 0) {((T*)(counter + 1))->~T(); pool128.dealloc(counter);} }
|
||||
|
||||
public:
|
||||
shared_ptr() : counter(nullptr) {}
|
||||
@ -307,7 +304,7 @@ public:
|
||||
|
||||
template <typename T, typename... Args>
|
||||
shared_ptr<T> make_sp(Args&&... args) {
|
||||
int* p = (int*)SP_MALLOC(sizeof(int) + sizeof(T));
|
||||
int* p = (int*)pool128.alloc(sizeof(int) + sizeof(T));
|
||||
*p = 1;
|
||||
new(p+1) T(std::forward<Args>(args)...);
|
||||
return shared_ptr<T>(p);
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
namespace pkpy{
|
||||
|
||||
const std::vector<uint16_t> kHashSeeds = {9629, 43049, 13267, 59509, 39251, 1249, 27689, 9719, 19913};
|
||||
const uint16_t kHashSeeds[] = {9629, 43049, 13267, 59509, 39251, 1249, 27689, 9719, 19913};
|
||||
|
||||
#define _hash(key, mask, hash_seed) ( ( (key).index * (hash_seed) >> 8 ) & (mask) )
|
||||
|
||||
@ -15,7 +15,8 @@ inline uint16_t find_perfect_hash_seed(uint16_t capacity, const std::vector<StrN
|
||||
static std::set<uint16_t> indices;
|
||||
indices.clear();
|
||||
std::pair<uint16_t, float> best_score = {kHashSeeds[0], 0.0f};
|
||||
for(int i=0; i<kHashSeeds.size(); i++){
|
||||
const int kHashSeedsSize = sizeof(kHashSeeds) / sizeof(kHashSeeds[0]);
|
||||
for(int i=0; i<kHashSeedsSize; i++){
|
||||
indices.clear();
|
||||
for(auto key: keys){
|
||||
uint16_t index = _hash(key, capacity-1, kHashSeeds[i]);
|
||||
|
@ -115,15 +115,6 @@ struct Slice {
|
||||
Slice(PyObject* start, PyObject* stop, PyObject* step) : start(start), stop(stop), step(step) {}
|
||||
};
|
||||
|
||||
class BaseIter {
|
||||
protected:
|
||||
VM* vm;
|
||||
public:
|
||||
BaseIter(VM* vm) : vm(vm) {}
|
||||
virtual PyObject* next() = 0;
|
||||
virtual ~BaseIter() = default;
|
||||
};
|
||||
|
||||
struct GCHeader {
|
||||
bool enabled; // whether this object is managed by GC
|
||||
bool marked; // whether this object is marked
|
||||
|
@ -223,10 +223,7 @@ inline void init_builtins(VM* _vm) {
|
||||
return VAR(r);
|
||||
});
|
||||
|
||||
_vm->bind__iter__(_vm->tp_range, [](VM* vm, PyObject* obj) {
|
||||
return vm->PyIter(RangeIter(vm, obj));
|
||||
});
|
||||
|
||||
_vm->bind__iter__(_vm->tp_range, [](VM* vm, PyObject* obj) { return VAR_T(RangeIter, OBJ_GET(Range, obj)); });
|
||||
_vm->bind__repr__(_vm->_type("NoneType"), [](VM* vm, PyObject* obj) { return VAR("None"); });
|
||||
_vm->bind__json__(_vm->_type("NoneType"), [](VM* vm, PyObject* obj) { return VAR("null"); });
|
||||
|
||||
@ -373,7 +370,7 @@ inline void init_builtins(VM* _vm) {
|
||||
return self.index(other) != -1;
|
||||
});
|
||||
_vm->bind__str__(_vm->tp_str, [](VM* vm, PyObject* obj) { return obj; });
|
||||
_vm->bind__iter__(_vm->tp_str, [](VM* vm, PyObject* obj) { return vm->PyIter(StringIter(vm, obj)); });
|
||||
_vm->bind__iter__(_vm->tp_str, [](VM* vm, PyObject* obj) { return VAR_T(StringIter, obj); });
|
||||
_vm->bind__repr__(_vm->tp_str, [](VM* vm, PyObject* obj) {
|
||||
const Str& self = _CAST(Str&, obj);
|
||||
return VAR(self.escape(true));
|
||||
@ -551,7 +548,8 @@ inline void init_builtins(VM* _vm) {
|
||||
return (i64)_CAST(List&, obj).size();
|
||||
});
|
||||
_vm->bind__iter__(_vm->tp_list, [](VM* vm, PyObject* obj) {
|
||||
return vm->PyIter(ArrayIter<List>(vm, obj));
|
||||
List& self = _CAST(List&, obj);
|
||||
return VAR_T(ArrayIter, obj, self.begin(), self.end());
|
||||
});
|
||||
_vm->bind__getitem__(_vm->tp_list, PyArrayGetItem<List>);
|
||||
_vm->bind__setitem__(_vm->tp_list, [](VM* vm, PyObject* obj, PyObject* index, PyObject* value){
|
||||
@ -584,13 +582,13 @@ inline void init_builtins(VM* _vm) {
|
||||
return x;
|
||||
});
|
||||
|
||||
_vm->bind__iter__(_vm->tp_tuple, [](VM* vm, PyObject* self) {
|
||||
return vm->PyIter(ArrayIter<Tuple>(vm, self));
|
||||
_vm->bind__iter__(_vm->tp_tuple, [](VM* vm, PyObject* obj) {
|
||||
Tuple& self = _CAST(Tuple&, obj);
|
||||
return VAR_T(ArrayIter, obj, self.begin(), self.end());
|
||||
});
|
||||
_vm->bind__getitem__(_vm->tp_tuple, PyArrayGetItem<Tuple>);
|
||||
_vm->bind__len__(_vm->tp_tuple, [](VM* vm, PyObject* self) {
|
||||
const Tuple& tuple = _CAST(Tuple&, self);
|
||||
return (i64)tuple.size();
|
||||
_vm->bind__len__(_vm->tp_tuple, [](VM* vm, PyObject* obj) {
|
||||
return (i64)_CAST(Tuple&, obj).size();
|
||||
});
|
||||
|
||||
/************ bool ************/
|
||||
@ -1068,7 +1066,7 @@ struct ReMatch {
|
||||
ReMatch(i64 start, i64 end, std::cmatch m) : start(start), end(end), m(m) {}
|
||||
|
||||
static void _register(VM* vm, PyObject* mod, PyObject* type){
|
||||
vm->bind_constructor<-1>(type, CPP_NOT_IMPLEMENTED());
|
||||
vm->bind_notimplemented_constructor<ReMatch>(type);
|
||||
vm->bind_method<0>(type, "start", CPP_LAMBDA(VAR(_CAST(ReMatch&, args[0]).start)));
|
||||
vm->bind_method<0>(type, "end", CPP_LAMBDA(VAR(_CAST(ReMatch&, args[0]).end)));
|
||||
|
||||
|
46
src/vm.h
46
src/vm.h
@ -48,19 +48,6 @@ inline int set_read_file_cwd(ReadFileCwdFunc func) { _read_file_cwd = func; retu
|
||||
inline PyObject* py_var(VM* vm, ctype&& value) { return vm->heap.gcnew(vm->ptype, std::move(value));}
|
||||
|
||||
|
||||
class Generator final: public BaseIter {
|
||||
Frame frame;
|
||||
int state; // 0,1,2
|
||||
List s_backup;
|
||||
public:
|
||||
Generator(VM* vm, Frame&& frame, ArgsView buffer): BaseIter(vm), frame(std::move(frame)), state(0) {
|
||||
for(PyObject* obj: buffer) s_backup.push_back(obj);
|
||||
}
|
||||
|
||||
PyObject* next() override;
|
||||
void _gc_mark() const;
|
||||
};
|
||||
|
||||
struct PyTypeInfo{
|
||||
PyObject* obj;
|
||||
Type base;
|
||||
@ -144,7 +131,7 @@ public:
|
||||
// for quick access
|
||||
Type tp_object, tp_type, tp_int, tp_float, tp_bool, tp_str;
|
||||
Type tp_list, tp_tuple;
|
||||
Type tp_function, tp_native_func, tp_iterator, tp_bound_method;
|
||||
Type tp_function, tp_native_func, tp_bound_method;
|
||||
Type tp_slice, tp_range, tp_module;
|
||||
Type tp_super, tp_exception, tp_bytes, tp_mappingproxy;
|
||||
Type tp_dict;
|
||||
@ -189,7 +176,6 @@ public:
|
||||
}
|
||||
|
||||
PyObject* py_iter(PyObject* obj){
|
||||
if(is_type(obj, tp_iterator)) return obj;
|
||||
const PyTypeInfo* ti = _inst_type_info(obj);
|
||||
if(ti->m__iter__) return ti->m__iter__(this, obj);
|
||||
PyObject* self;
|
||||
@ -477,12 +463,20 @@ public:
|
||||
|
||||
template<typename T, typename __T>
|
||||
PyObject* bind_default_constructor(__T&& type) {
|
||||
return bind_constructor<-1>(std::forward<__T>(type), [](VM* vm, ArgsView args){
|
||||
return bind_constructor<1>(std::forward<__T>(type), [](VM* vm, ArgsView args){
|
||||
Type t = OBJ_GET(Type, args[0]);
|
||||
return vm->heap.gcnew<T>(t, T());
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T, typename __T>
|
||||
PyObject* bind_notimplemented_constructor(__T&& type) {
|
||||
return bind_constructor<-1>(std::forward<__T>(type), [](VM* vm, ArgsView args){
|
||||
vm->NotImplementedError();
|
||||
return vm->None;
|
||||
});
|
||||
}
|
||||
|
||||
template<int ARGC>
|
||||
PyObject* bind_builtin_func(Str name, NativeFuncC fn) {
|
||||
return bind_func<ARGC>(builtins, name, fn);
|
||||
@ -496,17 +490,9 @@ public:
|
||||
return index;
|
||||
}
|
||||
|
||||
template<typename P>
|
||||
PyObject* PyIter(P&& value) {
|
||||
static_assert(std::is_base_of_v<BaseIter, std::decay_t<P>>);
|
||||
return heap.gcnew<P>(tp_iterator, std::forward<P>(value));
|
||||
}
|
||||
|
||||
PyObject* PyIterNext(PyObject* obj){
|
||||
if(is_non_tagged_type(obj, tp_iterator)){
|
||||
BaseIter* iter = static_cast<BaseIter*>(obj->value());
|
||||
return iter->next();
|
||||
}
|
||||
const PyTypeInfo* ti = _inst_type_info(obj);
|
||||
if(ti->m__next__) return ti->m__next__(this, obj);
|
||||
return call_method(obj, __next__);
|
||||
}
|
||||
|
||||
@ -600,6 +586,7 @@ public:
|
||||
void _error(Exception);
|
||||
PyObject* _run_top_frame();
|
||||
void post_init();
|
||||
PyObject* _py_generator(Frame&& frame, ArgsView buffer);
|
||||
};
|
||||
|
||||
inline PyObject* NativeFunc::operator()(VM* vm, ArgsView args) const{
|
||||
@ -1049,7 +1036,6 @@ inline void VM::init_builtin_types(){
|
||||
tp_module = _new_type_object("module");
|
||||
tp_function = _new_type_object("function");
|
||||
tp_native_func = _new_type_object("native_func");
|
||||
tp_iterator = _new_type_object("iterator");
|
||||
tp_bound_method = _new_type_object("bound_method");
|
||||
tp_super = _new_type_object("super");
|
||||
tp_exception = _new_type_object("Exception");
|
||||
@ -1255,12 +1241,10 @@ inline PyObject* VM::_py_call(PyObject** p0, PyObject* callable, ArgsView args,
|
||||
|
||||
if(co->is_generator){
|
||||
s_data.reset(p0);
|
||||
PyObject* ret = PyIter(Generator(
|
||||
this,
|
||||
return _py_generator(
|
||||
Frame(&s_data, nullptr, co, fn._module, callable),
|
||||
ArgsView(buffer, buffer + co_nlocals)
|
||||
));
|
||||
return ret;
|
||||
);
|
||||
}
|
||||
|
||||
// copy buffer back to stack
|
||||
|
Loading…
x
Reference in New Issue
Block a user