blob: ba12ee7c72d1c12217ebbd2a1f5f70d6a414ae78 [file] [log] [blame]
// Copyright 2017 The Wuffs 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.
package cgen
import (
"fmt"
"math/big"
"strconv"
a "github.com/google/wuffs/lang/ast"
t "github.com/google/wuffs/lang/token"
)
type funk struct {
bPrologue buffer
bBodyResume buffer
bBody buffer
bBodySuspend buffer
bEpilogue buffer
astFunc *a.Func
cName string
coroID uint32
returnsStatus bool
varList []*a.Var
varResumables map[t.ID]bool
derivedVars map[t.ID]struct{}
jumpTargets map[a.Loop]string
coroSuspPoint uint32
ioBinds uint32
tempW uint32
tempR uint32
usesEmptyIOBuffer bool
usesScratch bool
hasGotoOK bool
}
func (k *funk) jumpTarget(tm *t.Map, n a.Loop) (string, error) {
if label := n.Label(); label != 0 {
return label.Str(tm), nil
}
if k.jumpTargets == nil {
k.jumpTargets = map[a.Loop]string{}
}
if jt, ok := k.jumpTargets[n]; ok {
return jt, nil
}
jtInt := len(k.jumpTargets)
if jtInt == 1000000 {
return "", fmt.Errorf("too many jump targets")
}
jt := strconv.Itoa(jtInt)
k.jumpTargets[n] = jt
return jt, nil
}
func (g *gen) funcCName(n *a.Func) string {
if r := n.Receiver(); !r.IsZero() {
// TODO: this isn't right if r[0] != 0, i.e. the receiver is from a
// used package. There might be similar cases elsewhere in this
// package.
return g.pkgPrefix + r[1].Str(g.tm) + "__" + n.FuncName().Str(g.tm)
}
return g.pkgPrefix + n.FuncName().Str(g.tm)
}
// writeFunctionSignature modes.
const (
wfsCDecl = 0
wfsCDeclChoosy = 1
wfsCppDecl = 2
wfsCFuncPtrField = 3
wfsCFuncPtrFieldChoosy = 4
wfsCFuncPtrType = 5
)
func (g *gen) writeFuncSignature(b *buffer, n *a.Func, wfs uint32) error {
switch wfs {
case wfsCDecl:
if n.Public() {
b.writes("WUFFS_BASE__MAYBE_STATIC ")
} else {
b.writes("static ")
}
case wfsCDeclChoosy:
b.writes("static ")
case wfsCppDecl:
b.writes(" inline ")
case wfsCFuncPtrField, wfsCFuncPtrFieldChoosy, wfsCFuncPtrType:
// No-op.
}
// TODO: write n's return values.
if n.Effect().Coroutine() {
b.writes("wuffs_base__status")
} else if out := n.Out(); out == nil {
b.writes("wuffs_base__empty_struct")
// TODO: does writeCTypeName generate the right C if out is an array?
} else if err := g.writeCTypeName(b, out, "", ""); err != nil {
return err
}
switch wfs {
case wfsCDecl, wfsCDeclChoosy:
b.writes("\n")
case wfsCppDecl:
b.writes("\n ")
case wfsCFuncPtrField, wfsCFuncPtrFieldChoosy:
b.writes(" ")
}
comma := false
switch wfs {
case wfsCDecl, wfsCDeclChoosy:
b.writes(g.funcCName(n))
if wfs == wfsCDeclChoosy {
b.writes("__choosy_default")
}
b.writeb('(')
if r := n.Receiver(); !r.IsZero() {
b.writes("\n ")
if n.Effect().Pure() {
b.writes("const ")
}
b.printf("%s%s* self", g.pkgPrefix, r[1].Str(g.tm))
comma = true
}
case wfsCppDecl:
b.writes(n.FuncName().Str(g.tm))
b.writeb('(')
if len(n.In().Fields()) > 0 {
b.writes("\n ")
}
case wfsCFuncPtrField, wfsCFuncPtrFieldChoosy, wfsCFuncPtrType:
b.writes("(*")
if wfs == wfsCFuncPtrField {
b.writes(n.FuncName().Str(g.tm))
b.writes(")(\n ")
} else if wfs == wfsCFuncPtrFieldChoosy {
b.writes("choosy_")
b.writes(n.FuncName().Str(g.tm))
b.writes(")(\n ")
} else {
b.writes(")(")
}
if n.Effect().Pure() {
b.writes("const ")
}
if wfs == wfsCFuncPtrField {
b.writes("void* self")
} else if wfs == wfsCFuncPtrFieldChoosy {
b.printf("%s%s* self", g.pkgPrefix, n.Receiver()[1].Str(g.tm))
} else {
b.writes("void*")
}
comma = true
}
for _, o := range n.In().Fields() {
if comma {
b.writes(",\n ")
if wfs == wfsCppDecl {
b.writes(" ")
}
}
comma = true
o := o.AsField()
varNamePrefix, varName := "", ""
if wfs != wfsCFuncPtrType {
varNamePrefix, varName = aPrefix, o.Name().Str(g.tm)
}
if err := g.writeCTypeName(b, o.XType(), varNamePrefix, varName); err != nil {
return err
}
}
b.printf(")")
if (wfs == wfsCppDecl) && !n.Receiver().IsZero() && n.Effect().Pure() {
b.writes(" const")
}
return nil
}
func (g *gen) writeFuncPrototype(b *buffer, n *a.Func) error {
caMacro, _, _, err := cpuArchCNames(n.Asserts())
if err != nil {
return err
}
if caMacro != "" {
b.printf("#if defined(WUFFS_BASE__CPU_ARCH__%s)\n", caMacro)
}
if err := g.writeFuncSignature(b, n, wfsCDecl); err != nil {
return err
}
b.writes(";\n")
if caMacro != "" {
b.printf("#endif // defined(WUFFS_BASE__CPU_ARCH__%s)\n", caMacro)
}
b.writes("\n")
if n.Choosy() {
if err := g.writeFuncSignature(b, n, wfsCDeclChoosy); err != nil {
return err
}
b.writes(";\n\n")
}
return nil
}
func (g *gen) writeFuncImpl(b *buffer, n *a.Func) error {
k := g.funks[n.QQID()]
caMacro, caName, caAttribute, err := cpuArchCNames(n.Asserts())
if err != nil {
return err
}
if caName != "" {
b.printf("// ‼ WUFFS MULTI-FILE SECTION +%s\n", caName)
}
b.printf("// -------- func %s.%s\n\n", g.pkgName, n.QQID().Str(g.tm))
if caMacro != "" {
b.printf("#if defined(WUFFS_BASE__CPU_ARCH__%s)\n", caMacro)
}
if caAttribute != "" {
b.printf("%s\n", caAttribute)
}
if err := g.writeFuncSignature(b, n, wfsCDecl); err != nil {
return err
}
b.writes(" {\n")
if n.Choosy() {
b.printf("return (*self->private_impl.choosy_%s)(self", n.FuncName().Str(g.tm))
for _, o := range n.In().Fields() {
b.printf(", %s%s", aPrefix, o.AsField().Name().Str(g.tm))
}
b.writes(");\n}\n\n")
if err := g.writeFuncSignature(b, n, wfsCDeclChoosy); err != nil {
return err
}
b.writes(" {\n")
}
if (len(n.Body()) != 0) || n.Effect().Coroutine() || (n.Out() != nil) {
b.writex(k.bPrologue)
if n.Effect().Coroutine() {
b.writex(k.bBodyResume)
}
b.writex(k.bBody)
if n.Effect().Coroutine() {
b.writex(k.bBodySuspend)
} else if k.hasGotoOK {
b.writes("\nok:\n")
}
}
b.writex(k.bEpilogue)
b.writes("}\n")
if caMacro != "" {
b.printf("#endif // defined(WUFFS_BASE__CPU_ARCH__%s)\n", caMacro)
}
if caName != "" {
b.printf("// ‼ WUFFS MULTI-FILE SECTION -%s\n", caName)
}
b.writes("\n")
return nil
}
func (g *gen) gatherFuncImpl(_ *buffer, n *a.Func) error {
coroID := uint32(0)
if n.Public() && n.Effect().Coroutine() {
g.numPublicCoroutines[n.Receiver()]++
coroID = g.numPublicCoroutines[n.Receiver()]
if coroID >= 0x8000 {
return fmt.Errorf("too many coroutines for %q", n.Receiver().Str(g.tm))
}
}
g.currFunk = funk{
astFunc: n,
cName: g.funcCName(n),
coroID: coroID,
returnsStatus: n.Effect().Coroutine() ||
((n.Out() != nil) && n.Out().IsStatus()),
}
if err := g.findVars(); err != nil {
return err
}
g.findDerivedVars()
if err := g.writeFuncImplBody(&g.currFunk.bBody); err != nil {
return err
}
if err := g.writeFuncImplPrologue(&g.currFunk.bPrologue); err != nil {
return err
}
if err := g.writeFuncImplBodyResume(&g.currFunk.bBodyResume); err != nil {
return err
}
if err := g.writeFuncImplBodySuspend(&g.currFunk.bBodySuspend); err != nil {
return err
}
if err := g.writeFuncImplEpilogue(&g.currFunk.bEpilogue); err != nil {
return err
}
if g.currFunk.tempW != g.currFunk.tempR {
return fmt.Errorf("internal error: temporary variable count out of sync")
}
g.funks[n.QQID()] = g.currFunk
return nil
}
func writeOutParamZeroValue(b *buffer, tm *t.Map, typ *a.TypeExpr) error {
if typ == nil {
b.writes("wuffs_base__make_empty_struct()")
return nil
} else if typ.IsNumType() {
b.writes("0")
return nil
} else if typ.IsSliceType() {
if inner := typ.Inner(); (inner.Decorator() == 0) && (inner.QID() == t.QID{t.IDBase, t.IDU8}) {
b.writes("wuffs_base__make_slice_u8(NULL, 0)")
return nil
}
} else if (typ.Decorator() == 0) && (typ.QID()[0] == t.IDBase) {
switch typ.QID()[1] {
case t.IDRangeIEU32:
b.writes("wuffs_base__utility__empty_range_ie_u32()")
return nil
case t.IDRangeIIU32:
b.writes("wuffs_base__utility__empty_range_ii_u32()")
return nil
case t.IDRangeIEU64:
b.writes("wuffs_base__utility__empty_range_ie_u64()")
return nil
case t.IDRangeIIU64:
b.writes("wuffs_base__utility__empty_range_ii_u64()")
return nil
case t.IDRectIEU32:
b.writes("wuffs_base__utility__empty_rect_ie_u32()")
return nil
case t.IDRectIIU32:
b.writes("wuffs_base__utility__empty_rect_ii_u32()")
return nil
}
}
return fmt.Errorf("internal error: cannot write the zero value of type %q", typ.Str(tm))
}
func writeFuncImplSelfMagicCheck(b *buffer, tm *t.Map, f *a.Func) error {
returnsStatus := f.Effect().Coroutine() ||
((f.Out() != nil) && f.Out().IsStatus())
b.writes(" if (!self) {\n return ")
if returnsStatus {
b.writes("wuffs_base__make_status(wuffs_base__error__bad_receiver)")
} else if err := writeOutParamZeroValue(b, tm, f.Out()); err != nil {
return err
}
b.writes(";\n }\n")
if f.Effect().Pure() {
b.writes(" if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&\n")
b.writes(" (self->private_impl.magic != WUFFS_BASE__DISABLED)) {\n")
} else {
b.writes(" if (self->private_impl.magic != WUFFS_BASE__MAGIC) {\n")
}
b.writes(" return ")
if returnsStatus {
b.writes("wuffs_base__make_status(\n" +
" (self->private_impl.magic == WUFFS_BASE__DISABLED)\n" +
" ? wuffs_base__error__disabled_by_previous_error\n" +
" : wuffs_base__error__initialize_not_called)")
} else if err := writeOutParamZeroValue(b, tm, f.Out()); err != nil {
return err
}
b.writes(";\n }\n")
return nil
}
func (g *gen) writeFuncImplPrologue(b *buffer) error {
oldLenB := len(*b)
// Check the initialized/disabled state and the "self" arg.
if g.currFunk.astFunc.Public() && !g.currFunk.astFunc.Receiver().IsZero() {
if err := writeFuncImplSelfMagicCheck(b, g.tm, g.currFunk.astFunc); err != nil {
return err
}
}
// For public functions, check (at runtime) the other args for bounds and
// null-ness. For private functions, those checks are done at compile time.
if g.currFunk.astFunc.Public() {
if err := g.writeFuncImplArgChecks(b, g.currFunk.astFunc); err != nil {
return err
}
// For public coroutines, check that we are not suspended in an active
// coroutine.
if g.currFunk.astFunc.Effect().Coroutine() {
b.printf("if ((self->private_impl.active_coroutine != 0) &&\n"+
"(self->private_impl.active_coroutine != %d)) {\n", g.currFunk.coroID)
b.writes("self->private_impl.magic = WUFFS_BASE__DISABLED;\n")
b.writes("return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls);\n")
b.writes("}\n")
b.writes("self->private_impl.active_coroutine = 0;\n")
}
}
if g.currFunk.astFunc.Effect().Coroutine() ||
(g.currFunk.returnsStatus && (len(g.currFunk.derivedVars) > 0)) {
// TODO: rename the "status" variable to "ret"?
b.printf("wuffs_base__status status = wuffs_base__make_status(NULL);\n")
}
if oldLenB != len(*b) {
b.writes("\n")
}
// Generate the local variables.
oldLenB = len(*b)
if err := g.writeVars(b, &g.currFunk, false); err != nil {
return err
}
if oldLenB != len(*b) {
b.writes("\n")
}
if g.currFunk.derivedVars != nil {
for _, o := range g.currFunk.astFunc.In().Fields() {
o := o.AsField()
if _, ok := g.currFunk.derivedVars[o.Name()]; ok {
if err := g.writeInitialLoadDerivedVar(b, o); err != nil {
return err
}
}
}
b.writes("\n")
}
return nil
}
func (g *gen) writeFuncImplBodyResume(b *buffer) error {
if g.currFunk.coroSuspPoint > 0 {
// TODO: don't hard-code [0], and allow recursive coroutines.
b.printf("uint32_t coro_susp_point = self->private_impl.%s%s[0];\n",
pPrefix, g.currFunk.astFunc.FuncName().Str(g.tm))
resumeBuffer := buffer{}
if err := g.writeResumeSuspend(&resumeBuffer, &g.currFunk, false); err != nil {
return err
}
if len(resumeBuffer) > 0 {
b.writes("if (coro_susp_point) {\n")
b.writex(resumeBuffer)
b.writes("}\n")
}
// Generate a coroutine switch similiar to the technique in
// https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
//
// The matching } is written below. See "Close the coroutine switch".
b.writes("switch (coro_susp_point) {\nWUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;\n\n")
}
return nil
}
func (g *gen) writeFuncImplBody(b *buffer) error {
for _, o := range g.currFunk.astFunc.Body() {
if err := g.writeStatement(b, o, 0); err != nil {
return err
}
}
return nil
}
func (g *gen) writeFuncImplBodySuspend(b *buffer) error {
if (g.currFunk.coroSuspPoint > 0) || g.currFunk.astFunc.Effect().Coroutine() {
if !g.currFunk.hasGotoOK {
b.writes("\ngoto ok;") // Avoid the "unused label" warning.
}
b.writes("\nok:\n")
}
if g.currFunk.coroSuspPoint > 0 {
// We've reached the end of the function body. Reset the coroutine
// suspension point so that the next call to this function starts at
// the top.
b.printf("self->private_impl.%s%s[0] = 0;\n",
pPrefix, g.currFunk.astFunc.FuncName().Str(g.tm))
b.writes("goto exit;\n}\n\n") // Close the coroutine switch.
b.writes("goto suspend;\nsuspend:\n") // The goto avoids the "unused label" warning.
b.printf("self->private_impl.%s%s[0] = "+
"wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;\n",
pPrefix, g.currFunk.astFunc.FuncName().Str(g.tm))
if g.currFunk.astFunc.Public() {
b.printf("self->private_impl.active_coroutine = "+
"wuffs_base__status__is_suspension(&status) ? %d : 0;\n", g.currFunk.coroID)
}
if err := g.writeResumeSuspend(b, &g.currFunk, true); err != nil {
return err
}
b.writes("\n")
}
return nil
}
func (g *gen) writeFuncImplEpilogue(b *buffer) error {
epilogue := ""
if g.currFunk.astFunc.Effect().Coroutine() ||
(g.currFunk.returnsStatus && (len(g.currFunk.derivedVars) > 0)) {
b.writes("goto exit;\nexit:\n") // The goto avoids the "unused label" warning.
if g.currFunk.astFunc.Public() {
epilogue = "if (wuffs_base__status__is_error(&status)) {\n" +
"self->private_impl.magic = WUFFS_BASE__DISABLED;\n}\n" +
"return status;\n"
} else {
epilogue = "return status;\n"
}
} else if g.currFunk.astFunc.Out() == nil {
epilogue = "return wuffs_base__make_empty_struct();\n"
}
if (epilogue == "") && g.currFunk.astFunc.BodyEndsWithReturn() {
// No-op.
} else if g.currFunk.derivedVars != nil {
for _, o := range g.currFunk.astFunc.In().Fields() {
o := o.AsField()
if _, ok := g.currFunk.derivedVars[o.Name()]; ok {
if err := g.writeFinalSaveDerivedVar(b, o); err != nil {
return err
}
}
}
b.writes("\n")
}
b.writes(epilogue)
return nil
}
func (g *gen) writeFuncImplArgChecks(b *buffer, n *a.Func) error {
checks := []string(nil)
for _, o := range n.In().Fields() {
o := o.AsField()
oTyp := o.XType()
// TODO: Also check elements, for array-typed arguments.
switch {
case oTyp.IsIOTokenType() || (oTyp.Decorator() == t.IDPtr):
checks = append(checks, fmt.Sprintf("!%s%s", aPrefix, o.Name().Str(g.tm)))
case oTyp.IsRefined():
bounds := [2]*big.Int{}
for i, bound := range oTyp.Bounds() {
if bound != nil {
if cv := bound.ConstValue(); cv != nil {
bounds[i] = cv
}
}
}
if qid := oTyp.QID(); qid[0] == t.IDBase {
if key := qid[1]; key < t.ID(len(numTypeBounds)) {
ntb := numTypeBounds[key]
for i := 0; i < 2; i++ {
if bounds[i] != nil && ntb[i] != nil && bounds[i].Cmp(ntb[i]) == 0 {
bounds[i] = nil
continue
}
}
}
}
for i, bound := range bounds {
if bound != nil {
op := '<'
if i != 0 {
op = '>'
}
checks = append(checks, fmt.Sprintf("%s%s %c %s", aPrefix, o.Name().Str(g.tm), op, bound))
}
}
}
}
if len(checks) == 0 {
return nil
}
b.writes("if (")
for i, c := range checks {
if i != 0 {
b.writes(" || ")
}
b.writes(c)
}
b.writes(") {\n")
b.writes("self->private_impl.magic = WUFFS_BASE__DISABLED;\n")
if g.currFunk.astFunc.Effect().Coroutine() {
b.writes("return wuffs_base__make_status(wuffs_base__error__bad_argument);\n")
} else {
// TODO: don't assume that the return type is empty.
b.printf("return wuffs_base__make_empty_struct();\n")
}
b.writes("}\n")
return nil
}
var numTypeBounds = [...][2]*big.Int{
t.IDI8: {big.NewInt(-1 << 7), big.NewInt(1<<7 - 1)},
t.IDI16: {big.NewInt(-1 << 15), big.NewInt(1<<15 - 1)},
t.IDI32: {big.NewInt(-1 << 31), big.NewInt(1<<31 - 1)},
t.IDI64: {big.NewInt(-1 << 63), big.NewInt(1<<63 - 1)},
t.IDU8: {zero, big.NewInt(0).SetUint64(1<<8 - 1)},
t.IDU16: {zero, big.NewInt(0).SetUint64(1<<16 - 1)},
t.IDU32: {zero, big.NewInt(0).SetUint64(1<<32 - 1)},
t.IDU64: {zero, big.NewInt(0).SetUint64(1<<64 - 1)},
t.IDBool: {zero, one},
}