Skip to content

Commit

Permalink
Allow natives to return structs and arrays.
Browse files Browse the repository at this point in the history
Natives can now return enum structs and fixed-size arrays. The address
is passed as a hidden first parameter. For example:

    native Struct DoStuff(int x, int y);

Will have:

    params[0] = 3
    params[1] = the address of the output struct
    params[2] = x
    params[3] = y

The native MUST return params[1] on success, otherwise, behavior is
undefined.

It is recommended that when defining C++ definitions of SourcePawn
structs, to use `#pragma pack(push, 1)` and `#pragma pack(pop)`. In
addition, the only types that should appear are "cell_t" and "float". No
other type should be used for scalars. For arrays, "char" may be used,
but not as a scalar.

When using char arrays, the C++ definition must be careful to convert
the char size to a cell-aligned size. For example:

    enum struct MyStruct {
        int x;
        int y;
        char message[50];
    }

Should look like this in C++:

    #pragma pack(push, 1)
    struct MyStruct {
        cell_t x;
        cell_t y;
        char message[CharArraySize<50>::bytes];
    };
    #pragma pack(pop)
  • Loading branch information
dvander committed Oct 26, 2024
1 parent dc49bce commit 6788699
Show file tree
Hide file tree
Showing 13 changed files with 122 additions and 17 deletions.
52 changes: 49 additions & 3 deletions compiler/code-generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1139,7 +1139,7 @@ CodeGenerator::EmitFieldAccessExpr(FieldAccessExpr* expr)

void CodeGenerator::EmitCallExpr(CallExpr* call) {
// If returning an array, push a hidden parameter.
if (call->fun()->return_array()) {
if (call->fun()->return_array() && !call->fun()->is_native()) {
cell retsize = call->fun()->return_type()->CellStorageSize();
assert(retsize);

Expand Down Expand Up @@ -1208,12 +1208,58 @@ void CodeGenerator::EmitCallExpr(CallExpr* call) {
__ emit(OP_PUSH_PRI);
}

EmitCall(call->fun(), (cell)argv.size());
cell_t hidden_args = 0;
if (call->fun()->return_array() && call->fun()->is_native()) {
EmitNativeCallHiddenArg(call);
hidden_args++;
}

EmitCall(call->fun(), (cell)argv.size() + hidden_args);

if (call->fun()->return_array())
if (call->fun()->return_array() && !call->fun()->is_native())
__ emit(OP_POP_PRI);
}

void CodeGenerator::EmitNativeCallHiddenArg(CallExpr* call) {
TrackTempHeapAlloc(call, 1);

auto fun = call->fun();

ArrayData array;
BuildCompoundInitializer(fun->return_type(), nullptr, &array);

cell retsize = call->fun()->return_type()->CellStorageSize();
assert(retsize);

__ emit(OP_HEAP, retsize * sizeof(cell));

auto info = fun->return_array();
if (array.iv.empty()) {
__ emit(OP_CONST_PRI, 0);
__ emit(OP_FILL, retsize);
} else {
if (!info->iv_size) {
// No initializer, so we should have no data.
assert(array.data.empty());
assert(array.zeroes);

info->iv_size = (cell_t)array.iv.size();
info->dat_addr = data_.dat_address();
info->zeroes = array.zeroes;
data_.Add(std::move(array.iv));
}

cell dat_addr = info->dat_addr;
cell iv_size = info->iv_size;
assert(iv_size);
assert(info->zeroes);

__ emit(OP_INITARRAY_ALT, dat_addr, iv_size, 0, info->zeroes, 0);
}

__ emit(OP_PUSH_ALT);
}

void
CodeGenerator::EmitDefaultArgExpr(DefaultArgExpr* expr)
{
Expand Down
1 change: 1 addition & 0 deletions compiler/code-generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class CodeGenerator final
void EmitIndexExpr(IndexExpr* expr);
void EmitFieldAccessExpr(FieldAccessExpr* expr);
void EmitCallExpr(CallExpr* expr);
void EmitNativeCallHiddenArg(CallExpr* expr);
void EmitDefaultArgExpr(DefaultArgExpr* expr);
void EmitCallUserOpExpr(CallUserOpExpr* expr);
void EmitNewArrayExpr(NewArrayExpr* expr);
Expand Down
4 changes: 2 additions & 2 deletions compiler/messages.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ static const char* errmsg[] = {
/*036*/ "empty statement\n",
/*037*/ "invalid string (possibly non-terminated string)\n",
/*038*/ "extra characters on line\n",
/*039*/ "unused39\n",
/*039*/ "natives can only return fixed-size arrays\n",
/*040*/ "duplicate \"case\" label (value %d)\n",
/*041*/ "invalid ellipsis, array size is not known\n",
/*042*/ "invalid combination of class specifiers\n",
Expand Down Expand Up @@ -165,7 +165,7 @@ static const char* errmsg[] = {
/*138*/ "const was specified twice\n",
/*139*/ "could not find type \"%s\"\n",
/*140*/ "function '%s' does not return a value\n",
/*141*/ "natives, forwards, and public functions cannot return arrays\n",
/*141*/ "forwards and public functions cannot return arrays\n",
/*142*/ "unexpected array expression\n",
/*143*/ "new-style declarations should not have \"new\"\n",
/*144*/ "void cannot be used as a variable type\n",
Expand Down
6 changes: 0 additions & 6 deletions compiler/name-resolution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -790,12 +790,6 @@ bool FunctionDecl::Bind(SemaContext& outer_sc) {
markusage(args_[0], uREAD);
}

if ((is_native_ || is_public_ || is_forward_) &&
(return_type()->isArray() || return_type()->isEnumStruct()))
{
error(pos_, 141);
}

ok &= BindArgs(sc);

if (body_)
Expand Down
23 changes: 20 additions & 3 deletions compiler/semantics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2522,6 +2522,24 @@ bool Semantics::CheckCompoundReturnStmt(ReturnStmt* stmt) {
return true;
}

bool Semantics::CheckNativeCompoundReturn(FunctionDecl* info) {
auto rt = info->return_type();
if (auto root = rt->as<ArrayType>()) {
for (auto it = root; it; it = it->inner()->as<ArrayType>()) {
if (it->size() == 0) {
report(info, 39);
return false;
}
}
}

// For native calls, the implicit arg is first, not last.
auto rai = new FunctionDecl::ReturnArrayInfo;
rai->hidden_address = sizeof(cell_t) * 3;
info->set_return_array(rai);
return true;
}

bool Semantics::CheckAssertStmt(AssertStmt* stmt) {
if (Expr* expr = AnalyzeForTest(stmt->expr())) {
stmt->set_expr(expr);
Expand Down Expand Up @@ -2857,10 +2875,9 @@ bool Semantics::CheckFunctionDeclImpl(FunctionDecl* info) {
}

if (info->is_native()) {
if (decl.type.dim_exprs.size() > 0) {
report(info->pos(), 83);
auto rt = info->return_type();
if ((rt->isArray() || rt->isEnumStruct()) && !CheckNativeCompoundReturn(info))
return false;
}
return true;
}

Expand Down
1 change: 1 addition & 0 deletions compiler/semantics.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ class Semantics final
bool CheckStaticAssertStmt(StaticAssertStmt* stmt);
bool CheckReturnStmt(ReturnStmt* stmt);
bool CheckCompoundReturnStmt(ReturnStmt* stmt);
bool CheckNativeCompoundReturn(FunctionDecl* info);
bool CheckExprStmt(ExprStmt* stmt);
bool CheckIfStmt(IfStmt* stmt);
bool CheckConstDecl(ConstDecl* decl);
Expand Down
6 changes: 6 additions & 0 deletions include/sp_typeutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,10 @@ sp_ctof(cell_t val)
return sp::FloatCellUnion(val).f32;
}

template <size_t Size>
struct CharArraySize {
static constexpr size_t cells = (Size + sizeof(cell_t) - 1) / sizeof(cell_t);
static constexpr size_t bytes = cells * sizeof(cell_t);
};

#endif //_INCLUDE_SOURCEPAWN_VM_TYPEUTIL_H_
4 changes: 2 additions & 2 deletions tests/compile-only/fail-newdecls-2.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
(1) : error 141: natives, forwards, and public functions cannot return arrays
(2) : error 141: natives, forwards, and public functions cannot return arrays
(1) : error 039: natives can only return fixed-size arrays
(2) : error 141: forwards and public functions cannot return arrays
2 changes: 2 additions & 0 deletions tests/enum-structs/native-return.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x: 4
y: 6
8 changes: 8 additions & 0 deletions tests/enum-structs/native-return.sp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <shell>

public main()
{
TestStruct a = {1, 2};
TestStruct b = {3, 4};
print_test_struct(add_test_structs(a, b));
}
1 change: 1 addition & 0 deletions tests/shell.inc
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ native void copy_2d_array_to_callback(const int[] flat_array, int length, int st
native void assert_eq(any a1, any a2);

native void print_test_struct(TestStruct x);
native TestStruct add_test_structs(TestStruct a, TestStruct b);
2 changes: 1 addition & 1 deletion third_party/amtl
29 changes: 29 additions & 0 deletions vm/shell/shell.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,24 @@ static cell_t PrintTestStruct(IPluginContext* cx, const cell_t* params) {
return 0;
}

static cell_t AddTestStructs(IPluginContext* cx, const cell_t* params) {
TestStruct* a;
TestStruct* b;
TestStruct* out;

int err;
if ((err = cx->LocalToPhysAddr(params[1], reinterpret_cast<cell_t**>(&out))) != SP_ERROR_NONE)
return cx->ThrowNativeErrorEx(err, "Could not read out argument");
if ((err = cx->LocalToPhysAddr(params[2], reinterpret_cast<cell_t**>(&a))) != SP_ERROR_NONE)
return cx->ThrowNativeErrorEx(err, "Could not read argument 1");
if ((err = cx->LocalToPhysAddr(params[3], reinterpret_cast<cell_t**>(&b))) != SP_ERROR_NONE)
return cx->ThrowNativeErrorEx(err, "Could not read argument 2");

out->x = a->x + b->x;
out->y = a->y + b->y;
return params[1];
}

class DynamicNative : public INativeCallback
{
public:
Expand All @@ -353,6 +371,16 @@ class DynamicNative : public INativeCallback
uintptr_t refcount_ = 0;
};

#pragma pack(push, 1)
struct LayoutVerifier {
char message[CharArraySize<50>::bytes];
int x;
};
#pragma pack(pop)

static_assert(offsetof(LayoutVerifier, message) == 0);
static_assert(offsetof(LayoutVerifier, x) == 52);

static int Execute(const char* file)
{
char error[255];
Expand Down Expand Up @@ -386,6 +414,7 @@ static int Execute(const char* file)
BindNative(rt, "assert_eq", AssertEq);
BindNative(rt, "printf", Printf);
BindNative(rt, "print_test_struct", PrintTestStruct);
BindNative(rt, "add_test_structs", AddTestStructs);

IPluginFunction* fun = rt->GetFunctionByName("main");
if (!fun)
Expand Down

0 comments on commit 6788699

Please sign in to comment.