Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.compilers > #1171 > unrolled thread

Trouble implementing a standard library -- Calling C functions from within my language

Started bynoitalmost <noitalmost@cox.net>
First post2014-03-19 18:28 -0400
Last post2014-03-21 14:49 -0400
Articles 4 — 3 participants

Back to article view | Back to comp.compilers


Contents

  Trouble implementing a standard library -- Calling C functions from within my language noitalmost <noitalmost@cox.net> - 2014-03-19 18:28 -0400
    Re: Trouble implementing a standard library -- Calling C functions from within my language Hans-Peter Diettrich <DrDiettrich1@aol.com> - 2014-03-20 03:37 +0100
    Re: Trouble implementing a standard library -- Calling C functions from within my language alexfrunews@gmail.com - 2014-03-19 21:41 -0700
    Re: Trouble implementing a standard library -- Calling C functions from within my language noitalmost <noitalmost@cox.net> - 2014-03-21 14:49 -0400

#1171 — Trouble implementing a standard library -- Calling C functions from within my language

Fromnoitalmost <noitalmost@cox.net>
Date2014-03-19 18:28 -0400
SubjectTrouble implementing a standard library -- Calling C functions from within my language
Message-ID<14-03-049@comp.compilers>
I need some small-scale examples, like a toy compiler would do if it
needed access to some libc functions.

Let's say I have a module (library) called Std that provides
procedure ln (x : float) float;
procedure open(fname : string; mode : int) int;
procedure read(fd : int; buf : ^byte; cnt : int) bool;

On a Linux system, I want to just wrap the libc functions
double log(double x);
int open(const char *fname, int mode);
ssize_t read(int fd, void *buf, int cnt);

In prior projects, I always output assembly and just did system calls.
This time it's different. First, I have an intermediate form that can
produce C source code. Second, I want to be able to access functions
(say from math.h) that aren't Linux system calls.

I don't think the intermediate form matters for this discussion. It
can be assumed that module Std gets parsed into an AST, and that I can
walk the AST to generate C code.

How is this usually handled? I could put a CCall node in my AST and have the
parser recognize
procedure ln (x : float) :
  return CCall( ln, x );
end ln;

And then have
class CCall : public Expr
{
  CCall(string name, vector<Expr*> params);
  string genC();
  ...
}

where, Expr is a AstNode. But CCall::genC() would be a really
complicated list of special cases. Maybe I also need to pass the C
return type and the C types of the parameters? And I'm on my way
toward an Ada-style foreign-function interface. Eek! Isn't there a
simpler way?

[toc] | [next] | [standalone]


#1172

FromHans-Peter Diettrich <DrDiettrich1@aol.com>
Date2014-03-20 03:37 +0100
Message-ID<14-03-050@comp.compilers>
In reply to#1171
noitalmost schrieb:

> And then have
> class CCall : public Expr
> {
>   CCall(string name, vector<Expr*> params);
>   string genC();
>   ...
> }
>
> where, Expr is a AstNode. But CCall::genC() would be a really
> complicated list of special cases.

Right now I see a problem with Expr (lval), when the C function
expects an pointer (rval). This should be handled at least for
variables, i.e.  Expr should include an hint on whether it *is* an
lval. When you produce C code, your data types already should match
the C definitions, else another transformation may be required for
passing parameters back and forth. All that should be built into Expr,
or a DataType member of Expr.

DoDi

[toc] | [prev] | [next] | [standalone]


#1173

Fromalexfrunews@gmail.com
Date2014-03-19 21:41 -0700
Message-ID<14-03-051@comp.compilers>
In reply to#1171
On Wednesday, March 19, 2014 3:28:13 PM UTC-7, noitalmost wrote:
> I need some small-scale examples, like a toy compiler would do if it
> needed access to some libc functions.
>
> Let's say I have a module (library) called Std that provides
> procedure ln (x : float) float;
> procedure open(fname : string; mode : int) int;
> procedure read(fd : int; buf : ^byte; cnt : int) bool;
>
> On a Linux system, I want to just wrap the libc functions
> double log(double x);
> int open(const char *fname, int mode);
> ssize_t read(int fd, void *buf, int cnt);
>
> In prior projects, I always output assembly and just did system calls.
> This time it's different. First, I have an intermediate form that can
> produce C source code. Second, I want to be able to access functions
> (say from math.h) that aren't Linux system calls.
>
> I don't think the intermediate form matters for this discussion. It
> can be assumed that module Std gets parsed into an AST, and that I can
> walk the AST to generate C code.
>
> How is this usually handled? I could put a CCall node in my AST and have the
> parser recognize
>
> procedure ln (x : float) :
>   return CCall( ln, x );
> end ln;
>
> And then have
>
> class CCall : public Expr
> {
>   CCall(string name, vector<Expr*> params);
>   string genC();
>   ...
> }
>
> where, Expr is a AstNode. But CCall::genC() would be a really
> complicated list of special cases. Maybe I also need to pass the C
> return type and the C types of the parameters? And I'm on my way
> toward an Ada-style foreign-function interface. Eek! Isn't there a
> simpler way?

I'm not sure where exactly the problem lies, but here goes some thinking
w.r.t. C declarations and code generation...

In my C compiler I represent C types in a kind of AST that has them
deconvoluted and straightened out, e.g.:

int a -> a: int
int *a -> a: * int
int a[2][3] -> a: [2] [3] int
struct Tag* a[1] -> a: [1] * struct Tag
char (*a[2])(int b, int c[5]) -> a: [2] * (b: int, c: * int) char
etc

I pretty much literally store this declaration/type info in a list. I do store
the brackets and parens along with the tokens designating the fundamental
types and the other necessary tokens (array dimensions, identifiers, stars,
etc). These brackets and parens let me know what I'm dealing with (arrays or
functions) and since they're balanced I can dive into them (for example, to
get function parameter info) or skip them (if I don't care about function
params at the moment).

This unwound type/declaration representation can be naturally turned back into
C declarations, if one's interested in generating them for reference or
(re)compilation.

Arbitrary complexity is possible. Structures complicate things a little. I
keep structure declarations separate from declarations that use structures.
E.g.:

// refers to an empty struct tagged as Tag at first:
struct Tag* a[1] -> a: [1] * struct Tag

// now we define the struct:
struct Tag { int dummy; } -> <something>: struct Tag { dummy: int }

// and after this the type of a is completed and made
// to refer to the declaration of the struct Tag with
// the {} body instead of referring to the empty struct

You may want to generate or use such declarations if you want to:
- use your prototypes instead of those from #include <math.h>
- do some type parameter/return value type checking

But if you're translating your language into compilable C, you need more than
just invoking a function. Where do you actually take function parameters from?
They must be C expressions, containing constants, variables, other function
calls and operators.

Perhaps, you need to naturally generate C code out of all your expressions and
other building blocks of your source language? If each of them is already
parsed into a (sub)tree or a (sub)stack (e.g. containing the expression in the
Reverse Polish Notation, ready for evaluation or code generation), it's not
much of a problem and it's likely easier than doing the same for the purpose
of generating assembly code.

Alex

[toc] | [prev] | [next] | [standalone]


#1178

Fromnoitalmost <noitalmost@cox.net>
Date2014-03-21 14:49 -0400
Message-ID<14-03-056@comp.compilers>
In reply to#1171
On Thursday, March 20, 2014 12:41:16 AM alexfrunews@gmail.com wrote:
> In my C compiler I represent C types in a kind of AST that has them
> deconvoluted and straightened out, e.g.:

Yeah, maybe I should do something like that. I need to think about this some
more. I don't think I quite know what I want. I think I need to rework my
genC() methods so that they generate a C-like AST.

> But if you're translating your language into compilable C, you need more
> than just invoking a function. Where do you actually take function
> parameters from? They must be C expressions, containing constants,
> variables, other function calls and operators.
>
> Perhaps, you need to naturally generate C code out of all your expressions
> and other building blocks of your source language? If each of them is
> already parsed into a (sub)tree or a (sub)stack (e.g. containing the
> expression in the Reverse Polish Notation, ready for evaluation or code
> generation), it's not much of a problem and it's likely easier than doing
> the same for the purpose of generating assembly code.

The nodes of my AST are instances of C++ classes. Each node has a (virtual)
genC() method that returns C source code as a string. It returns the code for
itself and all its children. With a lot of details omitted, I have something
like:

class AstNode {
  virtual string genC(AstNode *parent);
};

class Expr : public AstNode {
  string genC(AstNode *parent);
};

class Call : public Expr {
  Call(string name);
  void addArg(Expr *arg);
  string genC(AstNode *parent);

  string m_name;  // procedure name
  vector<Expr*> m_args;
  Type *m_retType;  // type checker fills this in
};

string Call::genC(AstNode *parent) {
  string s = m_name + "(";
  for (arg : m_args) s += arg->genC(this) + ", ";
  s += ")";
  return s;
}

class SymDef : public AstNode {
  // a named symbol, which goes into a symbol table
  Type* dataType();
  virtual string genCDecl();
};

string SymDef::genCDecl() {
  // return C declaration
  string s = dataType()->genC(this);
  s += " " + m_name;
  return s;
  // extra stuff to handle arrays has been omitted
}

class ProcDef : public SymDef {
  ProcDef(string name, Type *retType, SymTab *parent);
  void addParam(VarDef *p);
  SymDef* get(string name);
  string genC(AstNode *parent);

  SymTab m_symTab;
  vector<AstNode*> m_stmts;
};

string ProcDef::genC(AstNode *parent) {
  string s = m_retType->genC(this);
  s += " " + m_name + "(";
  for (p : params()) {  // for each parameter
    s += p->dataType()->genC(this) + " ";
    s += p->genC(this) + ", ";
  }
  s += ") {\n";
  for (decl : decls()) {  // for each local declaration
    // excludes parameters
    s += decl->genCDecl(this) + ";\n";
  }
  for (stmt : stmts()) {  // for each statement
    s += stmt->genC(this) + ";\n";
  }
  s += "}\n";
  return s;
}

ProcDef is a procedure definition. It has a symbol table of local declarations
(which includes the procedure parameters), and a list of statements.

Each Type is also an AstNode. Int, Float, etc, are predeclared. Static
instances for predeclared types are created at compile time and are in the
global symbol table.

string Type::genC(AstNode *parent) {
  if (this == &Int) return "long";
  if (this == &Float) return "double";
}

Array, Record, and Pointer derive from Type; Pointers essentially just add "
*", while arrays and records become "char *".

And I just found a bug in my code. I've ignored alignment of record fields in
the translation to intermediate form. :[

[toc] | [prev] | [standalone]


Back to top | Article view | comp.compilers


csiph-web