aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/cps/CodeGen.h10
-rw-r--r--include/cps/FunctionGen.h4
-rw-r--r--lib/CodeGen/CallGen.cc35
-rw-r--r--lib/CodeGen/ExpressionGen.cc5
-rw-r--r--lib/CodeGen/FunctionGen.cc37
-rw-r--r--tests/i/extern-c-struct-value/expected.txt2
-rw-r--r--tests/i/extern-c-struct-value/lib.c24
-rw-r--r--tests/i/extern-c-struct-value/notes.md4
-rw-r--r--tests/i/extern-c-struct-value/program.cps9
-rw-r--r--tests/iii/extern-struct-rejected/case.json4
-rw-r--r--tests/iii/extern-struct-rejected/notes.md4
-rw-r--r--tests/iii/extern-struct-rejected/program.cps4
12 files changed, 112 insertions, 30 deletions
diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h
index 8f6a57b..b31cd3d 100644
--- a/include/cps/CodeGen.h
+++ b/include/cps/CodeGen.h
@@ -217,13 +217,17 @@ class CodeGen {
llvm::Value *emitOperatorCall(const FuncSig &Signature, ExprAST *Left, ExprAST *Right = nullptr);
llvm::Value *emitOperatorValues(const FuncSig &Signature, llvm::Value *Left,
const TypeInfo *LeftType, llvm::Value *Right);
- llvm::Value *emitVariadicArgument(ExprAST *Expr, const std::string &Callee, size_t Position);
+ llvm::Value *emitVariadicArgument(ExprAST *Expr, const std::string &Callee, size_t Position,
+ const TypeInfo *&Promoted);
+ // Variadic, if given, receives the promoted type of each variadic argument.
bool marshalCallArgs(const FuncSig &Signature,
const std::vector<std::unique_ptr<ExprAST>> &ArgExprs,
int Line,
- std::vector<llvm::Value*> &Out);
+ std::vector<llvm::Value*> &Out,
+ std::vector<const TypeInfo *> *Variadic = nullptr);
bool marshalCallArgs(const FuncSig &Signature, const std::vector<ExprAST*> &ArgExprs,
- int Line, std::vector<llvm::Value*> &Out);
+ int Line, std::vector<llvm::Value*> &Out,
+ std::vector<const TypeInfo *> *Variadic = nullptr);
llvm::Value *symbolAddress(const SymbolInfo &Info);
void emitAssignment(const LValueInfo &Target, ExprAST *Expr,
const SymbolInfo *Symbol = nullptr);
diff --git a/include/cps/FunctionGen.h b/include/cps/FunctionGen.h
index 8b63d67..6c5c7c0 100644
--- a/include/cps/FunctionGen.h
+++ b/include/cps/FunctionGen.h
@@ -122,7 +122,9 @@ public:
llvm::Function *emitFunctionDef(FunctionDefAST *FuncAST, const std::function<void(StmtAST*)> &StmtEmitter, unsigned ScopeID);
- llvm::Value *emitCall(const FuncSig &Sig, const std::vector<llvm::Value*> &Args);
+ // Variadic holds the promoted types of an EXTERN call's variadic arguments.
+ llvm::Value *emitCall(const FuncSig &Sig, const std::vector<llvm::Value*> &Args,
+ const std::vector<const TypeInfo *> &Variadic = {});
void emitReturn(llvm::Value *RetVal);
};
diff --git a/lib/CodeGen/CallGen.cc b/lib/CodeGen/CallGen.cc
index b25ca12..a5567b7 100644
--- a/lib/CodeGen/CallGen.cc
+++ b/lib/CodeGen/CallGen.cc
@@ -23,15 +23,17 @@ using namespace cps;
bool CodeGen::marshalCallArgs(const FuncSig &Signature,
const std::vector<std::unique_ptr<ExprAST>> &ArgExprs,
int Line,
- std::vector<Value*> &Out) {
+ std::vector<Value*> &Out,
+ std::vector<const TypeInfo *> *Variadic) {
std::vector<ExprAST*> Args;
for (const auto &Arg : ArgExprs) Args.push_back(Arg.get());
- return marshalCallArgs(Signature, Args, Line, Out);
+ return marshalCallArgs(Signature, Args, Line, Out, Variadic);
}
bool CodeGen::marshalCallArgs(const FuncSig &Signature,
const std::vector<ExprAST*> &ArgExprs,
- int Line, std::vector<Value*> &Out) {
+ int Line, std::vector<Value*> &Out,
+ std::vector<const TypeInfo *> *Variadic) {
SaveAndRestore<int> Location(DiagnosticLine, Line ? Line : DiagnosticLine);
const FuncSig *Sig = &Signature;
const std::string &Callee = Sig->Name;
@@ -43,9 +45,11 @@ bool CodeGen::marshalCallArgs(const FuncSig &Signature,
std::vector<size_t> Transfers;
for (size_t i = 0; i < ArgExprs.size(); ++i) {
if (i >= Sig->Params.size()) {
- Value *V = emitVariadicArgument(ArgExprs[i], Callee, i + 1);
+ const TypeInfo *Promoted = nullptr;
+ Value *V = emitVariadicArgument(ArgExprs[i], Callee, i + 1, Promoted);
if (!V) return false;
Out.push_back(V);
+ if (Variadic) Variadic->push_back(Promoted);
continue;
}
const ParamSig &P = Sig->Params[i];
@@ -126,20 +130,33 @@ bool CodeGen::marshalCallArgs(const FuncSig &Signature,
// C default argument promotions for the variadic part of an EXTERN call; only
// C values are accepted, matching the explicit-cast rule for C/pseudocode.
-Value *CodeGen::emitVariadicArgument(ExprAST *Expr, const std::string &Callee, size_t Position) {
+// C structs and pointers to them pass unchanged (C11 6.5.2.2p6-7, 7.16.1.1p2).
+Value *CodeGen::emitVariadicArgument(ExprAST *Expr, const std::string &Callee, size_t Position,
+ const TypeInfo *&Promoted) {
const TypeInfo *Info = isWholeArrayVar(Expr) ? nullptr : getExprTypeInfo(Expr);
- // Pointers to C structs pass unchanged; records by value are not supported yet.
- if (!Info || Info->isRecord() || !Types->isExternType(Info)) {
+ const TypeInfo *Record = Info && Info->Kind == TypeKind::Pointer
+ ? Types->resolve(Info->Pointer.PointeeTypeName) : Info;
+ std::string Why;
+ if (Record && Record->isRecord() && !Record->IsClass && !Types->isCStruct(Record, &Why)) {
+ reportError("Variadic argument %zu to %s uses record %s, which is not a C struct: %s",
+ Position, Callee.c_str(), Record->Name.c_str(), Why.c_str());
+ return nullptr;
+ }
+ if (!Info || !Types->isExternType(Info)) {
reportError("Variadic argument %zu to %s must be a C value; cast it to a C type", Position, Callee.c_str());
return nullptr;
}
+ Promoted = Info;
Value *V = emitExpr(Expr);
if (!V || Info->Kind != TypeKind::CScalar) return V;
if (Info->isReal()) {
const TypeInfo *Double = resolveType("C_DOUBLE");
- return Info->CRank < Double->CRank ? convertNumericValue(V, Info, Double) : V;
+ if (Info->CRank >= Double->CRank) return V;
+ Promoted = Double;
+ return convertNumericValue(V, Info, Double);
}
- return convertNumericValue(V, Info, Types->promoteCScalar(Info));
+ Promoted = Types->promoteCScalar(Info);
+ return convertNumericValue(V, Info, Promoted);
}
// Resolve using static source types only; argument evaluation belongs to marshalling.
diff --git a/lib/CodeGen/ExpressionGen.cc b/lib/CodeGen/ExpressionGen.cc
index ece055a..d7a4ba7 100644
--- a/lib/CodeGen/ExpressionGen.cc
+++ b/lib/CodeGen/ExpressionGen.cc
@@ -231,9 +231,10 @@ Value *CodeGen::emitCall(const std::string &Callee,
}
std::vector<Value*> Args;
+ std::vector<const TypeInfo *> Variadic;
const FuncSig *Sig = resolveCall(Name, ArgExprs, Explicit);
- if (!Sig || !marshalCallArgs(*Sig, ArgExprs, Line, Args)) return nullptr;
- return FuncGen->emitCall(*Sig, Args);
+ if (!Sig || !marshalCallArgs(*Sig, ArgExprs, Line, Args, &Variadic)) return nullptr;
+ return FuncGen->emitCall(*Sig, Args, Variadic);
}
Value *CodeGen::emitAllocationSize(ExprAST *Expr) {
diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc
index b79698e..6895945 100644
--- a/lib/CodeGen/FunctionGen.cc
+++ b/lib/CodeGen/FunctionGen.cc
@@ -470,21 +470,40 @@ llvm::Value *FunctionGen::emitCall(const std::string &Callee, const std::vector<
return Exceptions.emitCall(CalleeF, Args, "calltmp");
}
-llvm::Value *FunctionGen::emitCall(const FuncSig &Sig, const std::vector<llvm::Value*> &Args) {
+llvm::Value *FunctionGen::emitCall(const FuncSig &Sig, const std::vector<llvm::Value*> &Args,
+ const std::vector<const TypeInfo *> &Variadic) {
if (BeforeCall) BeforeCall(Sig);
if (!Sig.IsExternal) return emitCall(Sig.LinkName, Args);
FunctionCallee Callee(Sig.ExternType, Module.getNamedValue(Sig.LinkName));
- // Variadic arguments arrive promoted and pass as they are.
- size_t Fixed = Sig.ExternABI.Fixed;
+ // A call with variadic arguments is arranged with their promoted types.
+ const CABISignature *ABISig = &Sig.ExternABI;
+ CABISignature CallABI;
+ if (!Variadic.empty()) {
+ std::vector<const TypeInfo *> ArgTypes;
+ for (const ParamSig &P : Sig.Params) ArgTypes.push_back(Types.resolve(P.TypeName));
+ ArgTypes.insert(ArgTypes.end(), Variadic.begin(), Variadic.end());
+ const TypeInfo *Ret = sameIdentifier(Sig.ReturnTypeName, "VOID")
+ ? nullptr : Types.resolve(Sig.ReturnTypeName);
+ std::string Err;
+ if (!ABI.arrange(Ret, ArgTypes, Sig.Params.size(), true, CallABI, Err)) {
+ reportError("Internal: cannot arrange the call to %s: %s", Sig.Name.c_str(), Err.c_str());
+ return nullptr;
+ }
+ ABISig = &CallABI;
+ }
+ if (Args.size() != ABISig->Args.size()) {
+ reportError("Internal: the call to %s has %zu arguments but %zu are arranged", Sig.Name.c_str(),
+ Args.size(), ABISig->Args.size());
+ return nullptr;
+ }
Value *SRet = nullptr;
- std::vector<Value *> IRArgs = lowerCABIArguments(
- Builder, Sig.ExternABI, ArrayRef<Value *>(Args).take_front(Fixed), SRet);
- IRArgs.insert(IRArgs.end(), Args.begin() + Fixed, Args.end());
+ std::vector<Value *> IRArgs = lowerCABIArguments(Builder, *ABISig, Args, SRet);
Value *Call = Sig.ExternType->getReturnType()->isVoidTy()
? Exceptions.emitCall(Callee, IRArgs) : Exceptions.emitCall(Callee, IRArgs, "calltmp");
- cast<CallBase>(Call)->setAttributes(Sig.ExternAttrs);
- cast<CallBase>(Call)->setCallingConv(Sig.ExternABI.CallingConv);
- Value *Result = lowerCABIResult(Builder, Sig.ExternABI, Call, SRet);
+ cast<CallBase>(Call)->setAttributes(ABISig == &Sig.ExternABI
+ ? Sig.ExternAttrs : getCABIAttributes(Context, CallABI));
+ cast<CallBase>(Call)->setCallingConv(ABISig->CallingConv);
+ Value *Result = lowerCABIResult(Builder, *ABISig, Call, SRet);
return Result ? Result : Call;
}
diff --git a/tests/i/extern-c-struct-value/expected.txt b/tests/i/extern-c-struct-value/expected.txt
index 19eef3c..f96c960 100644
--- a/tests/i/extern-c-struct-value/expected.txt
+++ b/tests/i/extern-c-struct-value/expected.txt
@@ -8,3 +8,5 @@
1.500000 2.000000
Q 0.250000 9
0.500000 1.250000 2.000000
+7.750000
+14
diff --git a/tests/i/extern-c-struct-value/lib.c b/tests/i/extern-c-struct-value/lib.c
index 94afc3f..d74a1d0 100644
--- a/tests/i/extern-c-struct-value/lib.c
+++ b/tests/i/extern-c-struct-value/lib.c
@@ -1,3 +1,5 @@
+#include <stdarg.h>
+
/* Each struct is passed or returned by value; the host C ABI decides how. */
struct Pair { int a, b; };
struct Vec2 { double x, y; };
@@ -22,3 +24,25 @@ struct Mixed make_mixed(char tag, double value, int count) {
return m;
}
struct Vec3f make_vec3f(float x, float y, float z) { struct Vec3f v = {x, y, z}; return v; }
+
+/* Each argument after n is a struct read back with va_arg. */
+double sum_vec2(int n, ...) {
+ va_list ap;
+ double total = 0;
+ va_start(ap, n);
+ for (int i = 0; i < n; ++i) {
+ struct Vec2 v = va_arg(ap, struct Vec2);
+ total += v.x + v.y;
+ }
+ va_end(ap);
+ return total;
+}
+
+int mixed_counts(int n, ...) {
+ va_list ap;
+ int total = 0;
+ va_start(ap, n);
+ for (int i = 0; i < n; ++i) total += va_arg(ap, struct Mixed).count;
+ va_end(ap);
+ return total;
+}
diff --git a/tests/i/extern-c-struct-value/notes.md b/tests/i/extern-c-struct-value/notes.md
index a614d7b..c694d08 100644
--- a/tests/i/extern-c-struct-value/notes.md
+++ b/tests/i/extern-c-struct-value/notes.md
@@ -2,6 +2,6 @@
Human review: pending. Expected output was independently traced by AI, not captured from cpsc.
-Basis: compiler extension (EXTERN "C", INCLUDE_OBJ of a user object); a record whose fields are all C types is passed and returned by value like the matching C struct (C11 6.5.2.2p7: arguments are assigned to the parameters; 6.8.6.4p3: the returned value is the function call's value).
+Basis: compiler extension (EXTERN "C", INCLUDE_OBJ of a user object); a record whose fields are all C types is passed and returned by value like the matching C struct (C11 6.5.2.2p7: arguments are assigned to the parameters; 6.8.6.4p3: the returned value is the function call's value; 6.5.2.2p6-7 and 7.16.1.1p2: default promotions leave structs unchanged and va_arg reads a struct type).
-Trace: the harness compiles `lib.c` with the host C compiler. pair_sum receives Pair {3, 4} and returns 3 + 4 = 7. vec2_dot receives {1.5, 2} and {4, 0.25}: 1.5 * 4 + 2 * 0.25 = 6 + 0.5 = 6.5, printed like other C doubles as `6.500000`. mixed_score receives bias -1, Mixed {'M', 2.5, 3} and k 4: -1 + 2.5 * 4 + 3 + 1 (the tag is 'M') = 13, printed `13.000000`. vec3f_sum receives {0.5, 1.25, 2}, all exact in float, and returns 0.5 + 1.25 + 2 = 3.75, printed `3.750000`. wide_after_five receives five ints and Wide {100, 1000}: 1 + 2 + 3 + 4 + 5 + 100 + 1000 = 1115. On x86-64 System V the five ints leave one integer register, so the 16-byte Wide goes on the stack. The make_ functions return the struct built from their arguments: make_pair(3, 4) gives `3 4`, and pair_sum(make_pair(5, 6)) passes the returned Pair straight on, 5 + 6 = 11. make_vec2(1.5, 2) gives `1.500000 2.000000`. make_mixed('Q', 0.25, 9) gives `Q 0.250000 9`; on x86-64 System V this 24-byte struct comes back through memory supplied by the caller. make_vec3f(0.5, 1.25, 2) gives `0.500000 1.250000 2.000000`, the C floats printed with %f.
+Trace: the harness compiles `lib.c` with the host C compiler. pair_sum receives Pair {3, 4} and returns 3 + 4 = 7. vec2_dot receives {1.5, 2} and {4, 0.25}: 1.5 * 4 + 2 * 0.25 = 6 + 0.5 = 6.5, printed like other C doubles as `6.500000`. mixed_score receives bias -1, Mixed {'M', 2.5, 3} and k 4: -1 + 2.5 * 4 + 3 + 1 (the tag is 'M') = 13, printed `13.000000`. vec3f_sum receives {0.5, 1.25, 2}, all exact in float, and returns 0.5 + 1.25 + 2 = 3.75, printed `3.750000`. wide_after_five receives five ints and Wide {100, 1000}: 1 + 2 + 3 + 4 + 5 + 100 + 1000 = 1115. On x86-64 System V the five ints leave one integer register, so the 16-byte Wide goes on the stack. The make_ functions return the struct built from their arguments: make_pair(3, 4) gives `3 4`, and pair_sum(make_pair(5, 6)) passes the returned Pair straight on, 5 + 6 = 11. make_vec2(1.5, 2) gives `1.500000 2.000000`. make_mixed('Q', 0.25, 9) gives `Q 0.250000 9`; on x86-64 System V this 24-byte struct comes back through memory supplied by the caller. make_vec3f(0.5, 1.25, 2) gives `0.500000 1.250000 2.000000`, the C floats printed with %f. sum_vec2 reads two struct Vec2 values with va_arg: U is still {1.5, 2} from make_vec2 and V is {4, 0.25}, so 1.5 + 2 + 4 + 0.25 = 7.75, printed `7.750000`. mixed_counts reads two struct Mixed values: M holds count 9 from make_mixed and N = make_mixed('R', 1, 5) holds count 5, so 9 + 5 = 14.
diff --git a/tests/i/extern-c-struct-value/program.cps b/tests/i/extern-c-struct-value/program.cps
index 6b2cb8e..c8b3a1f 100644
--- a/tests/i/extern-c-struct-value/program.cps
+++ b/tests/i/extern-c-struct-value/program.cps
@@ -1,4 +1,5 @@
-// Records whose fields are C types pass to and return from C functions by value.
+// Records whose fields are C types pass to and return from C functions by
+// value, also as variadic arguments.
INCLUDE_OBJ "lib.o"
TYPE CShort = C_TYPE("short")
TYPE CFloat = C_TYPE("float")
@@ -34,10 +35,13 @@ EXTERN "C" EXTERN_FUNCTION make_pair(C_INT, C_INT) RETURNS Pair
EXTERN "C" EXTERN_FUNCTION make_vec2(C_DOUBLE, C_DOUBLE) RETURNS Vec2
EXTERN "C" EXTERN_FUNCTION make_mixed(C_CHAR, C_DOUBLE, C_INT) RETURNS Mixed
EXTERN "C" EXTERN_FUNCTION make_vec3f(CFloat, CFloat, CFloat) RETURNS Vec3f
+EXTERN "C" EXTERN_FUNCTION sum_vec2(C_INT, ...) RETURNS C_DOUBLE
+EXTERN "C" EXTERN_FUNCTION mixed_counts(C_INT, ...) RETURNS C_INT
DECLARE P : Pair
DECLARE U : Vec2
DECLARE V : Vec2
DECLARE M : Mixed
+DECLARE N : Mixed
DECLARE F : Vec3f
DECLARE W : Wide
P.A <- (C_INT)3
@@ -68,3 +72,6 @@ M <- make_mixed((C_CHAR)'Q', (C_DOUBLE)0.25, (C_INT)9)
OUTPUT M.Tag, " ", M.Value, " ", M.Count
F <- make_vec3f((CFloat)0.5, (CFloat)1.25, (CFloat)2)
OUTPUT F.X, " ", F.Y, " ", F.Z
+OUTPUT sum_vec2((C_INT)2, U, V)
+N <- make_mixed((C_CHAR)'R', (C_DOUBLE)1, (C_INT)5)
+OUTPUT mixed_counts((C_INT)2, M, N)
diff --git a/tests/iii/extern-struct-rejected/case.json b/tests/iii/extern-struct-rejected/case.json
index f83ef3e..cd7a3dd 100644
--- a/tests/iii/extern-struct-rejected/case.json
+++ b/tests/iii/extern-struct-rejected/case.json
@@ -11,6 +11,10 @@
{
"reason": "The\\ return\\ type\\ of\\ EXTERN_FUNCTION\\ atoi\\ uses\\ record\\ Person,\\ which\\ is\\ not\\ a\\ C\\ struct:\\ field\\ Name\\ of\\ Person\\ has\\ type\\ STRING",
"line": 10
+ },
+ {
+ "reason": "Variadic\\ argument\\ 2\\ to\\ printf\\ uses\\ record\\ Person,\\ which\\ is\\ not\\ a\\ C\\ struct:\\ field\\ Name\\ of\\ Person\\ has\\ type\\ STRING",
+ "line": 13
}
]
}
diff --git a/tests/iii/extern-struct-rejected/notes.md b/tests/iii/extern-struct-rejected/notes.md
index 3e733c1..650b420 100644
--- a/tests/iii/extern-struct-rejected/notes.md
+++ b/tests/iii/extern-struct-rejected/notes.md
@@ -1,5 +1,5 @@
# extern-struct-rejected
-Compiler extension: an EXTERN signature accepts a record, through a pointer type or by value, only as a C struct (every field a C type, a C struct or a pointer type to one). Person has a STRING field, so a pointer to it, Person as a by-value parameter and Person as a by-value result are all rejected and the field is named.
+Compiler extension: an EXTERN signature accepts a record, through a pointer type or by value, only as a C struct (every field a C type, a C struct or a pointer type to one). Person has a STRING field, so a pointer to it, Person as a by-value parameter, as a by-value result and as a by-value variadic argument are all rejected and the field is named.
-Require exit 1 and each cause on its EXTERN line.
+Require exit 1 and each cause on its EXTERN or call line.
diff --git a/tests/iii/extern-struct-rejected/program.cps b/tests/iii/extern-struct-rejected/program.cps
index 8fd1fd9..ced6fee 100644
--- a/tests/iii/extern-struct-rejected/program.cps
+++ b/tests/iii/extern-struct-rejected/program.cps
@@ -8,4 +8,6 @@ TYPE PPerson = ^Person
EXTERN "C" EXTERN_PROCEDURE puts(PPerson)
EXTERN "C" EXTERN_FUNCTION abs(Person) RETURNS C_INT
EXTERN "C" EXTERN_FUNCTION atoi(C_STRING) RETURNS Person
-OUTPUT 1
+EXTERN "C" EXTERN_FUNCTION printf(C_STRING, ...) RETURNS C_INT
+DECLARE Someone : Person
+OUTPUT printf((C_STRING)"%d", Someone)