blob: 456e6fabdaf704f9e1badf7b5f17fac576401e35 [file] [log] [blame]
// Copyright 2017 The Puffs Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:generate go run gen.go
package cgen
import (
"bytes"
"fmt"
"math/big"
"os"
"os/exec"
"strings"
"github.com/google/puffs/lang/base38"
"github.com/google/puffs/lang/builtin"
"github.com/google/puffs/lang/check"
"github.com/google/puffs/lang/generate"
a "github.com/google/puffs/lang/ast"
t "github.com/google/puffs/lang/token"
)
var (
zero = big.NewInt(0)
one = big.NewInt(1)
)
// Prefixes are prepended to names to form a namespace and to avoid e.g.
// "double" being a valid Puffs variable name but not a valid C one.
const (
aPrefix = "a_" // Function argument.
bPrefix = "b_" // Derived from a function argument.
cPrefix = "c_" // Coroutine state.
fPrefix = "f_" // Struct field.
iPrefix = "i_" // Iterate variable.
lPrefix = "l_" // Limit length.
tPrefix = "t_" // Temporary local variable.
vPrefix = "v_" // Local variable.
)
// Do transpiles a Puffs program to a C program.
//
// The arguments list the source Puffs files. If no arguments are given, it
// reads from stdin.
//
// The generated program is written to stdout.
func Do(args []string) error {
return generate.Do(args, func(pkgName string, tm *t.Map, c *check.Checker, files []*a.File) ([]byte, error) {
g := &gen{
PKGPREFIX: "PUFFS_" + strings.ToUpper(pkgName) + "_",
pkgPrefix: "puffs_" + pkgName + "_",
pkgName: pkgName,
tm: tm,
checker: c,
files: files,
}
unformatted, err := g.generate()
if err != nil {
return nil, err
}
stdout := &bytes.Buffer{}
cmd := exec.Command("clang-format", "-style=Chromium")
cmd.Stdin = bytes.NewReader(unformatted)
cmd.Stdout = stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return nil, err
}
return stdout.Bytes(), nil
})
}
const (
maxNamespacedStatusCode = 255
statusCodeNamespaceMask = 1<<base38.MaxBits - 1
statusCodeNamespaceShift = 10
statusCodeCodeBits = 8
statusCodeDescription = "" +
"// Status codes are int32_t values:\n" +
"// - the sign bit indicates a non-recoverable status code: an error\n" +
"// - bits 10-30 hold the packageid: a namespace\n" +
"// - bits 8-9 are reserved\n" +
"// - bits 0-7 are a package-namespaced numeric code\n"
)
func init() {
// The +1 is for the error bit (the sign bit).
if statusCodeNamespaceShift+base38.MaxBits+1 != 32 {
panic("inconsistent status code namespace shift")
}
if len(builtin.StatusList) > maxNamespacedStatusCode {
panic("too many built-in statuses")
}
}
type replacementPolicy bool
const (
replaceNothing = replacementPolicy(false)
replaceCallSuspendibles = replacementPolicy(true)
)
// parenthesesPolicy controls whether to print the outer parentheses in an
// expression like "(x + y)". An "if" or "while" will print their own
// parentheses for "if (expr)" because they need to be able to say "if (x)".
// But a double-parenthesized expression like "if ((x == y))" is a clang
// warning (-Wparentheses-equality) and we like to compile with -Wall -Werror.
type parenthesesPolicy bool
const (
parenthesesMandatory = parenthesesPolicy(false)
parenthesesOptional = parenthesesPolicy(true)
)
type visibility uint32
const (
bothPubPri = visibility(iota)
pubOnly
priOnly
)
const maxTemp = 10000
type status struct {
name string
msg string
keyword t.ID
}
type buffer []byte
func (b *buffer) Write(p []byte) (int, error) {
*b = append(*b, p...)
return len(p), nil
}
func (b *buffer) printf(format string, args ...interface{}) { fmt.Fprintf(b, format, args...) }
func (b *buffer) writeb(x byte) { *b = append(*b, x) }
func (b *buffer) writes(s string) { *b = append(*b, s...) }
func (b *buffer) writex(s []byte) { *b = append(*b, s...) }
type gen struct {
PKGPREFIX string // e.g. "PUFFS_JPEG_"
pkgPrefix string // e.g. "puffs_jpeg_"
pkgName string // e.g. "jpeg"
tm *t.Map
checker *check.Checker
files []*a.File
statusList []status
statusMap map[t.ID]status
structList []*a.Struct
structMap map[t.ID]*a.Struct
currFunk funk
funks map[t.QID]funk
}
func (g *gen) generate() ([]byte, error) {
b := new(buffer)
g.statusMap = map[t.ID]status{}
if err := g.forEachStatus(b, bothPubPri, (*gen).gatherStatuses); err != nil {
return nil, err
}
// Make a topologically sorted list of structs.
unsortedStructs := []*a.Struct(nil)
for _, file := range g.files {
for _, tld := range file.TopLevelDecls() {
if tld.Kind() == a.KStruct {
unsortedStructs = append(unsortedStructs, tld.Struct())
}
}
}
var ok bool
g.structList, ok = a.TopologicalSortStructs(unsortedStructs)
if !ok {
return nil, fmt.Errorf("cyclical struct definitions")
}
g.structMap = map[t.ID]*a.Struct{}
for _, n := range g.structList {
g.structMap[n.Name()] = n
}
g.funks = map[t.QID]funk{}
if err := g.forEachFunc(nil, bothPubPri, (*gen).gatherFuncImpl); err != nil {
return nil, err
}
if err := g.genHeader(b); err != nil {
return nil, err
}
b.writes("// C HEADER ENDS HERE.\n\n")
if err := g.genImpl(b); err != nil {
return nil, err
}
return *b, nil
}
func (g *gen) genHeader(b *buffer) error {
includeGuard := "PUFFS_" + strings.ToUpper(g.pkgName) + "_H"
b.printf("#ifndef %s\n#define %s\n\n", includeGuard, includeGuard)
b.printf("// Code generated by puffs-c. DO NOT EDIT.\n\n")
b.writes(baseHeader)
b.writes("\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n")
b.writes("// ---------------- Status Codes\n\n")
b.writes(statusCodeDescription)
b.printf("//\n// Do not manipulate these bits directly. Use the API functions such as\n"+
"// %sstatus_is_error instead.\n", g.pkgPrefix)
b.printf("typedef int32_t %sstatus;\n\n", g.pkgPrefix)
pkgID := g.checker.PackageID()
b.printf("#define %spackageid %d // %#08x\n\n", g.pkgPrefix, pkgID, pkgID)
for i, z := range builtin.StatusList {
code := uint32(0)
if z.Keyword == t.IDError {
code |= 1 << 31
}
code |= uint32(i)
b.printf("#define %s %d // %#08x\n", strings.ToUpper(g.cName(z.String())), int32(code), code)
}
b.writes("\n")
if len(g.statusList) > maxNamespacedStatusCode {
return fmt.Errorf("too many status codes")
}
for i, s := range g.statusList {
code := pkgID << statusCodeNamespaceShift
if s.keyword.Key() == t.KeyError {
code |= 1 << 31
}
code |= uint32(i)
b.printf("#define %s %d // %#08x\n", s.name, int32(code), code)
}
b.writes("\n")
b.printf("bool %sstatus_is_error(%sstatus s);\n\n", g.pkgPrefix, g.pkgPrefix)
b.printf("const char* %sstatus_string(%sstatus s);\n\n", g.pkgPrefix, g.pkgPrefix)
b.writes("// ---------------- Public Consts\n\n")
if err := g.forEachConst(b, pubOnly, (*gen).writeConst); err != nil {
return err
}
b.writes("// ---------------- Structs\n\n")
for _, n := range g.structList {
if err := g.writeStruct(b, n); err != nil {
return err
}
}
b.writes("// ---------------- Public Initializer Prototypes\n\n")
for _, n := range g.structList {
if n.Public() {
if err := g.writeInitializerPrototype(b, n); err != nil {
return err
}
}
}
b.writes("// ---------------- Public Function Prototypes\n\n")
if err := g.forEachFunc(b, pubOnly, (*gen).writeFuncPrototype); err != nil {
return err
}
b.writes("\n#ifdef __cplusplus\n} // extern \"C\"\n#endif\n\n")
b.printf("#endif // %s\n\n", includeGuard)
return nil
}
func (g *gen) genImpl(b *buffer) error {
b.writes(baseImpl)
b.writes("\n")
b.writes("// ---------------- Status Codes Implementations\n\n")
b.printf("bool %sstatus_is_error(%sstatus s) { return s < 0; }\n\n", g.pkgPrefix, g.pkgPrefix)
b.printf("const char* %sstatus_strings0[%d] = {\n", g.pkgPrefix, len(builtin.StatusList))
for _, z := range builtin.StatusList {
b.printf("%q,", g.pkgName+": "+z.Message)
}
b.writes("};\n\n")
b.printf("const char* %sstatus_strings1[%d] = {\n", g.pkgPrefix, len(g.statusList))
for _, s := range g.statusList {
b.printf("%q,", g.pkgName+": "+s.msg)
}
b.writes("};\n\n")
b.printf("const char* %sstatus_string(%sstatus s) {\n", g.pkgPrefix, g.pkgPrefix)
b.printf("const char** a = NULL;\n")
b.printf("uint32_t n = 0;\n")
b.printf("switch ((s >> %d) & %#x) {\n", statusCodeNamespaceShift, statusCodeNamespaceMask)
b.printf("case 0: a = %sstatus_strings0; n = %d; break;\n", g.pkgPrefix, len(builtin.StatusList))
b.printf("case %spackageid: a = %sstatus_strings1; n = %d; break;\n",
g.pkgPrefix, g.pkgPrefix, len(g.statusList))
// TODO: add cases for other packages used by this one.
b.printf("}\n")
b.printf("uint32_t i = s & %#0x;\n", 1<<statusCodeCodeBits-1)
b.printf("return i < n ? a[i] : \"%s: unknown status\";\n", g.pkgName)
b.writes("}\n\n")
b.writes("// ---------------- Private Consts\n\n")
if err := g.forEachConst(b, priOnly, (*gen).writeConst); err != nil {
return err
}
b.writes("// ---------------- Private Initializer Prototypes\n\n")
for _, n := range g.structList {
if !n.Public() {
if err := g.writeInitializerPrototype(b, n); err != nil {
return err
}
}
}
b.writes("// ---------------- Private Function Prototypes\n\n")
if err := g.forEachFunc(b, priOnly, (*gen).writeFuncPrototype); err != nil {
return err
}
b.writes("// ---------------- Initializer Implementations\n\n")
b.writes("// PUFFS_MAGIC is a magic number to check that initializers are called. It's\n")
b.writes("// not foolproof, given C doesn't automatically zero memory before use, but it\n")
b.writes("// should catch 99.99% of cases.\n")
b.writes("//\n")
b.writes("// Its (non-zero) value is arbitrary, based on md5sum(\"puffs\").\n")
b.writes("#define PUFFS_MAGIC (0xCB3699CCU)\n\n")
b.writes("// PUFFS_ALREADY_ZEROED is passed from a container struct's initializer to a\n")
b.writes("// containee struct's initializer when the container has already zeroed the\n")
b.writes("// containee's memory.\n")
b.writes("//\n")
b.writes("// Its (non-zero) value is arbitrary, based on md5sum(\"zeroed\").\n")
b.writes("#define PUFFS_ALREADY_ZEROED (0x68602EF1U)\n\n")
for _, n := range g.structList {
if err := g.writeInitializerImpl(b, n); err != nil {
return err
}
}
b.writes("// ---------------- Function Implementations\n\n")
if err := g.forEachFunc(b, bothPubPri, (*gen).writeFuncImpl); err != nil {
return err
}
return nil
}
func (g *gen) forEachConst(b *buffer, v visibility, f func(*gen, *buffer, *a.Const) error) error {
for _, file := range g.files {
for _, tld := range file.TopLevelDecls() {
if tld.Kind() != a.KConst ||
(v == pubOnly && tld.Raw().Flags()&a.FlagsPublic == 0) ||
(v == priOnly && tld.Raw().Flags()&a.FlagsPublic != 0) {
continue
}
if err := f(g, b, tld.Const()); err != nil {
return err
}
}
}
return nil
}
func (g *gen) forEachFunc(b *buffer, v visibility, f func(*gen, *buffer, *a.Func) error) error {
for _, file := range g.files {
for _, tld := range file.TopLevelDecls() {
if tld.Kind() != a.KFunc ||
(v == pubOnly && tld.Raw().Flags()&a.FlagsPublic == 0) ||
(v == priOnly && tld.Raw().Flags()&a.FlagsPublic != 0) {
continue
}
if err := f(g, b, tld.Func()); err != nil {
return err
}
}
}
return nil
}
func (g *gen) forEachStatus(b *buffer, v visibility, f func(*gen, *buffer, *a.Status) error) error {
for _, file := range g.files {
for _, tld := range file.TopLevelDecls() {
if tld.Kind() != a.KStatus ||
(v == pubOnly && tld.Raw().Flags()&a.FlagsPublic == 0) ||
(v == priOnly && tld.Raw().Flags()&a.FlagsPublic != 0) {
continue
}
if err := f(g, b, tld.Status()); err != nil {
return err
}
}
}
return nil
}
func (g *gen) cName(name string) string {
s := []byte(nil)
s = append(s, "puffs_"...)
s = append(s, g.pkgName...)
s = append(s, '_')
underscore := true
for _, r := range name {
if 'A' <= r && r <= 'Z' {
s = append(s, byte(r+'a'-'A'))
underscore = false
} else if ('a' <= r && r <= 'z') || ('0' <= r && r <= '9') {
s = append(s, byte(r))
underscore = false
} else if !underscore {
s = append(s, '_')
underscore = true
}
}
if underscore {
s = s[:len(s)-1]
}
return string(s)
}
func (g *gen) sizeof(typ *a.TypeExpr) (uint32, error) {
if typ.Decorator() == 0 {
switch typ.Name().Key() {
case t.KeyU8:
return 1, nil
case t.KeyU16:
return 2, nil
case t.KeyU32:
return 4, nil
case t.KeyU64:
return 8, nil
}
}
return 0, fmt.Errorf("unknown sizeof for %q", typ.String(g.tm))
}
func (g *gen) gatherStatuses(b *buffer, n *a.Status) error {
raw := n.Message().String(g.tm)
msg, ok := t.Unescape(raw)
if !ok {
return fmt.Errorf("bad status message %q", raw)
}
prefix := "SUSPENSION_"
if n.Keyword().Key() == t.KeyError {
prefix = "ERROR_"
}
s := status{
name: strings.ToUpper(g.cName(prefix + msg)),
msg: msg,
keyword: n.Keyword(),
}
g.statusList = append(g.statusList, s)
g.statusMap[n.Message()] = s
return nil
}
func (g *gen) writeConst(b *buffer, n *a.Const) error {
if !n.Public() {
b.writes("static ")
}
b.writes("const ")
if err := g.writeCTypeName(b, n.XType(), g.pkgPrefix, n.Name().String(g.tm)); err != nil {
return err
}
b.writes(" = ")
if err := g.writeConstList(b, n.Value()); err != nil {
return err
}
b.writes(";\n\n")
return nil
}
func (g *gen) writeConstList(b *buffer, n *a.Expr) error {
switch n.ID0().Key() {
case 0:
b.writes(n.ConstValue().String())
case t.KeyDollar:
b.writeb('{')
for _, o := range n.Args() {
if err := g.writeConstList(b, o.Expr()); err != nil {
return err
}
b.writeb(',')
}
b.writeb('}')
default:
return fmt.Errorf("invalid const value %q", n.String(g.tm))
}
return nil
}
func (g *gen) writeStruct(b *buffer, n *a.Struct) error {
// For API/ABI compatibility, the very first field in the struct's
// private_impl must be the status code. This lets the initializer callee
// set "self->private_impl.status = etc_error_bad_puffs_version;"
// regardless of the sizeof(*self) struct reserved by the caller and even
// if the caller and callee were built with different versions.
structName := n.Name().String(g.tm)
b.writes("typedef struct {\n")
b.writes("// Do not access the private_impl's fields directly. There is no API/ABI\n")
b.writes("// compatibility or safety guarantee if you do so. Instead, use the\n")
b.printf("// %s%s_etc functions.\n", g.pkgPrefix, structName)
b.writes("//\n")
b.writes("// In C++, these fields would be \"private\", but C does not support that.\n")
b.writes("//\n")
b.writes("// It is a struct, not a struct*, so that it can be stack allocated.\n")
b.writes("struct {\n")
if n.Suspendible() {
b.printf("%sstatus status;\n", g.pkgPrefix)
b.writes("uint32_t magic;\n")
b.writes("\n")
}
for _, o := range n.Fields() {
o := o.Field()
if err := g.writeCTypeName(b, o.XType(), fPrefix, o.Name().String(g.tm)); err != nil {
return err
}
b.writes(";\n")
}
if n.Suspendible() {
b.writeb('\n')
for _, file := range g.files {
for _, tld := range file.TopLevelDecls() {
if tld.Kind() != a.KFunc {
continue
}
o := tld.Func()
if o.Receiver() != n.Name() || !o.Suspendible() {
continue
}
k := g.funks[o.QID()]
if k.coroSuspPoint == 0 && !k.usesScratch {
continue
}
// TODO: allow max depth > 1 for recursive coroutines.
const maxDepth = 1
b.writes("struct {\n")
if k.coroSuspPoint != 0 {
b.writes("uint32_t coro_susp_point;\n")
if err := g.writeVars(b, o.Body(), true, true); err != nil {
return err
}
}
if k.usesScratch {
b.writes("uint64_t scratch;\n")
}
b.printf("} %s%s[%d];\n", cPrefix, o.Name().String(g.tm), maxDepth)
}
}
}
b.printf("} private_impl;\n } %s%s;\n\n", g.pkgPrefix, structName)
return nil
}
func (g *gen) writeInitializerSignature(b *buffer, n *a.Struct, public bool) error {
structName := n.Name().String(g.tm)
if public {
b.printf("// %s%s_initialize is an initializer function.\n", g.pkgPrefix, structName)
b.printf("//\n")
b.printf("// It should be called before any other %s%s_* function.\n",
g.pkgPrefix, structName)
b.printf("//\n")
b.printf("// Pass PUFFS_VERSION and 0 for puffs_version and for_internal_use_only.\n")
}
b.printf("void %s%s_initialize(%s%s *self", g.pkgPrefix, structName, g.pkgPrefix, structName)
b.printf(", uint32_t puffs_version, uint32_t for_internal_use_only")
b.printf(")")
return nil
}
func (g *gen) writeInitializerPrototype(b *buffer, n *a.Struct) error {
if !n.Suspendible() {
return nil
}
if err := g.writeInitializerSignature(b, n, n.Public()); err != nil {
return err
}
b.writes(";\n\n")
return nil
}
func (g *gen) writeInitializerImpl(b *buffer, n *a.Struct) error {
if !n.Suspendible() {
return nil
}
if err := g.writeInitializerSignature(b, n, false); err != nil {
return err
}
b.printf("{\n")
b.printf("if (!self) { return; }\n")
b.printf("if (puffs_version != PUFFS_VERSION) {\n")
b.printf("self->private_impl.status = %sERROR_BAD_PUFFS_VERSION;\n", g.PKGPREFIX)
b.printf("return;\n")
b.printf("}\n")
b.writes("if (for_internal_use_only != PUFFS_ALREADY_ZEROED) {" +
"memset(self, 0, sizeof(*self)); }\n")
b.writes("self->private_impl.magic = PUFFS_MAGIC;\n")
for _, f := range n.Fields() {
f := f.Field()
if dv := f.DefaultValue(); dv != nil {
// TODO: set default values for array types.
b.printf("self->private_impl.%s%s = %d;\n", fPrefix, f.Name().String(g.tm), dv.ConstValue())
}
}
// Call any ctors on sub-structs.
for _, f := range n.Fields() {
f := f.Field()
x := f.XType()
if x != x.Innermost() {
// TODO: arrays of sub-structs.
continue
}
if g.structMap[x.Name()] == nil {
continue
}
b.printf("%s%s_initialize(&self->private_impl.%s%s,"+
"PUFFS_VERSION, PUFFS_ALREADY_ZEROED);\n",
g.pkgPrefix, x.Name().String(g.tm), fPrefix, f.Name().String(g.tm))
}
b.writes("}\n\n")
return nil
}