/* ir.c -- Generates Intermediate Representation (IR) Copyright © 2021-2024 Samuel Lidén Borell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include "internal.h" #include "ast.h" #include "backend.h" #define INTERR_IR(errnum) MAKE_INTERR(errnum, INTERRBASE_IR) #define INTERR_DEFSDONE INTERR_IR(0x01) #define INTERR_EMIT_BADTYPE INTERR_IR(0x02) #define INTERR_EMIT_TYPE INTERR_IR(0x03) #define INTERR_EMIT_FUNCDECL INTERR_IR(0x04) #define INTERR_ARRAYTOOLARGE INTERR_IR(0x05) #define INTERR_FUNCIR INTERR_IR(0x06) #define INTERR_EMIT_STMT INTERR_IR(0x07) #define INTERR_EMIT_EXPR INTERR_IR(0x08) #define INTERR_EMIT_BADEXPR INTERR_IR(0x09) #define INTERR_EMIT_CONDJUMP INTERR_IR(0x0A) #define INTERR_EMIT_COMPAREJUMP INTERR_IR(0x0B) #define INTERR_EMIT_JUMP INTERR_IR(0x0C) #define INTERR_EMIT_ASSIGN INTERR_IR(0x0D) #define INTERR_EMIT_OPASSIGN INTERR_IR(0x0E) #define INTERR_EMIT_STRINGLIT1 INTERR_IR(0x0F) #define INTERR_EMIT_STRINGLIT2 INTERR_IR(0x10) #define INTERR_EMIT_DATADEF INTERR_IR(0x11) #define INTERR_EMIT_EXPRDATA INTERR_IR(0x12) /** Gets the size (in elements, not bytes) of each dimension of the array type of the given array expression. This is used to determine how large offsets (in elements) to use when indexing into arrays. */ static int get_dimension_infos(struct CSlul *ctx, const struct ExprNode *arrayexpr, size_t indices) { struct DimensionInfo *dims, *dim; size_t left; struct TypeRef arrtr; uint64 rowlen; if (indices > MAXTYPEDEPTH) { assert(0); return 0; } assert(indices > 0); if (ctx->ir_dims_capacity < indices) { lowlvl_free(ctx->cfg, ctx->ir_dims_buffer); dims = lowlvl_alloc(ctx->cfg, indices * sizeof(struct DimensionInfo)); if (UNLIKELY(!dims)) { ctx->ir_dims_buffer = NULL; ctx->ir_dims_capacity = 0; return 0; } ctx->ir_dims_buffer = dims; ctx->ir_dims_capacity = indices; } dims = ctx->ir_dims_buffer; STRICTCHK(real_deref_opt_tr(ctx, &arrayexpr->u.tr, &arrtr)); STRICTCHK(arrtr.type->type); assert(arrtr.type->type == T_ARRAY); rowlen = 1; left = indices; dim = dims; for (;;) { struct ArrTypeInfo arrinfo; enum ArrInfoState state; struct Type *t = real_tr(ctx, &arrtr, &arrtr); if (UNLIKELY(!t)) return 0; if (t->type != T_ARRAY) break; state = NORMAL_ARRAYS; get_array_info(t, &arrinfo, &state); assert(!arrinfo.is_variable); if (left) { left--; /* Will be multiplied with the length of the length of any nested arrays in the next loop */ dim->rowlen = arrinfo.constant_len; dim++; } else { /* Nested array(s) that we're not indexing into */ rowlen *= arrinfo.constant_len; } arrtr.type = arrinfo.elemtype; /* XXX check what happens when indexing into arrays-of-pointers-to-arrays. this function and the code that calls it will not handle it properly*/ STRICTCHK(real_tr(ctx, &arrtr, &arrtr)); } assert(left == 0); left = indices; dim = &dims[indices]; while (left--) { assert(dim > dims); dim--; dim->elemlen = rowlen; dim->rowlen *= rowlen; rowlen = dim->rowlen; } assert(dim == dims); return 1; } #ifndef DISABLE_CSBE #define FIRST_VARLANE 1 static unsigned get_arraytype_length(const struct Type *arrtype) { assert(arrtype->misc != M_ANY_LENGTH); if (arrtype->misc != M_LONG_LENGTH) { return arrtype->misc; } else { struct ExprNode *le = arrtype->u.arr->lengthexpr->root; assert(le->exprtype == E_INTEGER); assert(le->a.intval < UINT_MAX); return le->a.intval; } } static const struct Type *get_arraytype_elemtype(const struct Type *arrtype) { if (arrtype->misc != M_LONG_LENGTH) { return arrtype->u.nested; } else { return &arrtype->u.arr->elemtype; } } /** * Extracts information from array types (possibly nested/multi-dimensional). * * \param ctx Compilation context. * \param csbe CSBE context. * \param type_ptr Input: Array type. Output: Element type. * \param arrlen_out Output: Total array length. * \return 1 if successful, 0 if not. */ static int handle_arraytypes(struct CSlul *ctx, struct Csbe *csbe, const struct Type **type_ptr, unsigned *arrlen_out) { unsigned arrlen = 1; const struct Type *elemtype = *type_ptr; enum CsbeErr e; for (;;) { struct TypeRef elemtr = root_tr_const(elemtype); const struct Type *realtype = real_type_tr(ctx, &elemtr); unsigned nestedlen; if (realtype->type != T_ARRAY) break; nestedlen = get_arraytype_length(realtype); ECHK(csbe_multiply_arraylengths(csbe, &arrlen, arrlen, nestedlen)); elemtype = get_arraytype_elemtype(realtype); } *type_ptr = elemtype; *arrlen_out = arrlen; return 1; err_csbe: interr_csbe(ctx, e, INTERR_ARRAYTOOLARGE); return 0; } static int is_bool_true(const struct ExprRoot *expr) { struct ExprNode *e = expr->root; assert(e); return e->exprtype == E_BOOL && e->a.intval == 1; } static const enum CsbeTypeKind elementary2csbetype[BT_NUMTYPES] = { CSBET_BOOL, CSBET_USIZE, CSBET_SSIZE, CSBET_U64, /* fileoffs. XXX: should this be a dedicated CSBE type? */ /* TODO time type */ CSBET_DPTR, CSBET_I8, CSBET_U8, CSBET_U8, CSBET_I16, CSBET_U16, CSBET_U16, CSBET_INT, CSBET_UINT, CSBET_UINT, CSBET_I32, CSBET_U32, CSBET_U32, CSBET_I64, CSBET_U64, CSBET_U64 }; static int emit_type(struct CSlul *ctx, struct Csbe *csbe, const struct Type *type) { enum CsbeErr e; enum CsbeTypeKind simpletype; deeper: CHECK_STRUCT(*type); switch (type->type) { case T_REF: case T_SLOT: /* TODO should use a special uint32_or_dptr type */ /* FIXME the qualifiers on the *nested* type should decide the threaded-ness/aliased-ness */ if ((type->quals & Q_THREADED) != 0) { simpletype = CSBET_DPTR_THREADED; } else if ((type->quals & Q_ALIASED) != 0) { simpletype = CSBET_DPTR_ALIASED; } else { simpletype = CSBET_DPTR; } break; case T_ARRAY: { unsigned arrlen; const struct Type *elemtype = type; ZCHK(handle_arraytypes(ctx, csbe, &elemtype, &arrlen)); csbe_type_array(csbe, arrlen); type = elemtype; goto deeper; } case T_STRUCT: { struct FieldOrParamEntry *f; /* TODO use CSBEPM_ANYPACK for private structs */ ECHK(csbe_type_struct_start(csbe, type->u.fields->count, CSBEPM_CPACK)); for (f = type->u.fields->first; f; f = f->next) { ZCHK(emit_type(ctx, csbe, &f->f.vardef.decl.type)); } ECHK(csbe_type_struct_end(csbe)); return 1; } case T_ENUM: /* FIXME type detection of private enums is not implemented yet */ /*type = &type->u.enu->base; goto deeper;*/ simpletype = CSBET_INT; break; case T_IDENT: case T_IMPORTED: { struct TopLevelType *tltype = (struct TopLevelType *)type->u.ident; ECHK(csbe_type_named(csbe, tltype->id)); return 1; } case T_ELMNTRY: simpletype = elementary2csbetype[type->u.builtin]; break; case T_BITS: /* TODO */ internal_error(ctx, INTERR_EMIT_BADTYPE); return 0; case T_FUNC: case T_METHOD: simpletype = CSBET_FPTR; break; case T_LIFETIMED: type = &type->u.lifetimed->type; goto deeper; case T_GENERICDEF: type = &type->u.gdef->basetype; goto deeper; case T_GENERICSPEC: type = type->u.gprm->generictype; goto deeper; case T_GENERICVAR: case T_PRIVATE: case T_INTERNAL: case T_INVALID: default: internal_error(ctx, INTERR_EMIT_BADTYPE); return 0; } csbe_type_simple(csbe, simpletype); return 1; err_csbe: return interr_csbe(ctx, e, INTERR_EMIT_TYPE); } static int emit_type_tr(struct CSlul *ctx, struct Csbe *csbe, const struct TypeRef *tr) { const struct Type *type = real_type_tr(ctx, tr); return emit_type(ctx, csbe, type); } /* Typedefs */ static int generate_typedef(struct CSlul *ctx, struct Csbe *csbe, struct TopLevelType *tltype) { CHECK_STRUCT(*tltype); CHECK_STRUCT(tltype->decl); if (tltype->decl.type.type != T_PRIVATE && tltype->decl.type.type != T_IMPORTED) { csbe_typedef_start(csbe, tltype->id); ZCHK(emit_type(ctx, csbe, &tltype->decl.type)); csbe_typedef_end(csbe); } return 1; } static int generate_typedefs(struct CSlul *ctx, struct Csbe *csbe, struct TopLevels *tl) { struct TopLevelType *tltype; for (tltype = tl->types_list; tltype; tltype = tltype->next) { ZCHK(generate_typedef(ctx, csbe, tltype)); } return 1; } static int generate_all_typedefs(struct CSlul *ctx, struct Csbe *csbe) { struct CSlulDependency *dep; csbe_set_num_typedefs(csbe, ctx->num_typedefs); ZCHK(generate_typedefs(ctx, csbe, &ctx->impl)); ZCHK(generate_typedefs(ctx, csbe, &ctx->module.iface)); /* TODO could emit only symbols that are actually used */ for (dep = ctx->module.first_dep; dep; dep = dep->next) { ZCHK(generate_typedefs(ctx, csbe, &dep->module->iface)); } return 1; } /* Data defs */ static int string_to_bytes(struct CSlul *ctx, const struct ExprNode *expr, const char **bufferp, size_t *lenp) { const struct StringLiteral *str = expr->a.strval; assert(str != NULL); if (str->data) { *bufferp = str->data; *lenp = str->u.length; return 1; } else if (str->u.chunks == NULL) { *bufferp = NULL; *lenp = 0; return 1; } else { char *buff; size_t len = 0; const struct StringChunk *chunk; for (chunk = str->u.chunks; chunk; chunk = chunk->next) { len += chunk->length; } *lenp = len; buff = aalloc(ctx, len, 1); if (!buff) return 0; *bufferp = buff; for (chunk = str->u.chunks; chunk; chunk = chunk->next) { memcpy(buff, chunk->data, chunk->length); buff += chunk->length; } return 1; } } static int emit_exprdata(struct CSlul *ctx, struct Csbe *csbe, const struct ExprNode *expr); static int emit_array_elems(struct CSlul *ctx, struct Csbe *csbe, const struct ExprNode *expr, unsigned char **elems8, uint64 **elems64) { struct ExprNode **elems = expr->a.exprlist->elems; unsigned numelems = expr->a.exprlist->length; while (numelems--) { const struct ExprNode *elem = *(elems++); if (elem->exprtype == E_ARRAY) { ZCHK(emit_array_elems(ctx, csbe, elem, elems8, elems64)); } else if (elems8) { unsigned v; assert(elem->exprtype == E_BOOL || elem->exprtype == E_INTEGER); v = elem->a.intval; if (elem->exprtype == E_INTEGER && INTLITERAL_IS_NEGATIVE(*elem)) { v = -v; } *((*elems8)++) = v; } else if (elems64) { uint64 v; assert(elem->exprtype == E_INTEGER); v = elem->a.intval; if (INTLITERAL_IS_NEGATIVE(*elem)) { v = -v; } *((*elems64)++) = v; } else { assert(elem->exprtype != E_BOOL && elem->exprtype != E_INTEGER); ZCHK(emit_exprdata(ctx, csbe, elem)); } } return 1; } static int emit_exprdata(struct CSlul *ctx, struct Csbe *csbe, const struct ExprNode *expr) { enum CsbeErr e; switch ((int)expr->exprtype) { case E_STRING: /* Strings are variable-length and can't go directly inside data structures */ assert(0); break; case E_BOOL: csbe_datadef_value_int(csbe, expr->a.intval); break; case E_INTEGER: if (!INTLITERAL_IS_NEGATIVE(*expr)) { csbe_datadef_value_int(csbe, expr->a.intval); } else { csbe_datadef_value_int(csbe, -expr->a.intval); } break; case E_ARRAY: { struct TypeRef arrtr; struct TypeRef elemtr; unsigned num_elems = 1; unsigned char *elems8 = NULL, *nextelem8; uint64 *elems64 = NULL, *nextelem64; /* Determine element type & array size */ if (!real_tr(ctx, &expr->u.tr, &arrtr)) assert(0); assert(arrtr.type->type == T_ARRAY); do { const struct Type *elemtype; unsigned dimlen = get_arraytype_length(arrtr.type); ECHK(csbe_multiply_arraylengths(csbe, &num_elems, num_elems, dimlen)); elemtype = get_arraytype_elemtype(arrtr.type); elemtr = nested_tr_const(elemtype, &arrtr); if (!real_tr(ctx, &elemtr, &elemtr)) assert(0); if (elemtr.type->type != T_ARRAY) break; /* Nested array */ arrtr = elemtr; } while (1); /* For scalar elements, we build an array and pass all elements in one go */ if (elemtr.type->type == T_ELMNTRY) { enum BuiltinType bt = elemtr.type->u.builtin; if (bt == BT_Bool || (bt >= BT_Int8 && bt <= BT_WUInt8)) { if (!num_elems) { csbe_datadef_value_bytearray(csbe, NULL, 0); return 1; } elems8 = aalloc(ctx, num_elems, 1); ZCHK(elems8); } else if (BT_IS_INTEGER(bt)) { unsigned size; if (!num_elems) { csbe_datadef_value_intarray(csbe, NULL, 0); return 1; } ECHK(csbe_multiply_arraylengths(csbe, &size, num_elems, sizeof(uint64))); elems64 = aalloc(ctx, size, sizeof(uint64)); ZCHK(elems64); } else assert(0); } else { assert(elemtr.type->type == T_STRUCT); } /* Add elements */ nextelem8 = elems8; nextelem64 = elems64; ZCHK(emit_array_elems(ctx, csbe, expr, elems8 ? &nextelem8 : NULL, elems64 ? &nextelem64 : NULL)); if (elems8) { csbe_datadef_value_bytearray(csbe, elems8, num_elems); } else if (elems64) { csbe_datadef_value_intarray(csbe, elems64, num_elems); } break; } case E_STRUCT: { size_t len = expr->a.exprlist->length; struct ExprNode **fieldp = expr->a.exprlist->elems; for (; len--; fieldp++) { const struct ExprNode *field = *fieldp; assert(field->exprtype == E_FIELDINIT); ZCHK(emit_exprdata(ctx, csbe, field->a.expr)); } break; } default: assert(0); return 0; } return 1; err_csbe: return interr_csbe(ctx, e, INTERR_EMIT_EXPRDATA); } static int emit_inline_string_type(struct CSlul *ctx, struct Csbe *csbe, unsigned len) { enum CsbeErr e; if (len <= 127) { ECHK(csbe_type_struct_start(csbe, 3, CSBEPM_CPACK)); csbe_type_simple(csbe, CSBET_U8); /* Length byte */ } else { ECHK(csbe_type_struct_start(csbe, 4, CSBEPM_CPACK)); csbe_type_simple(csbe, CSBET_U8); /* Flags */ csbe_type_simple(csbe, CSBET_U8); /* Reserved */ csbe_type_simple(csbe, CSBET_U16); /* Reserved (free space?) */ csbe_type_simple(csbe, CSBET_USIZE); /* Length */ /* There could be optional fields as well, depending on the flags: - capacity / free space at end - multi-chunk strings */ } ECHK(csbe_type_array(csbe, len)); csbe_type_simple(csbe, CSBET_U8); /* String data (array) */ csbe_type_simple(csbe, CSBET_U8); /* Null terminator */ ECHK(csbe_type_struct_end(csbe)); return 1; err_csbe: return interr_csbe(ctx, e, INTERR_EMIT_STRINGLIT1); } static int emit_stringliteral_buffer(struct CSlul *ctx, struct Csbe *csbe, const char *buffer, unsigned len) { enum CsbeErr e; /* Add type */ ZCHK(emit_inline_string_type(ctx, csbe, len)); /* Add bytes (read-only) */ ECHK(csbe_datadef_initval_start(csbe)); if (len <= 127) { csbe_datadef_value_int(csbe, len); } else { csbe_datadef_value_int(csbe, 0x80); /* Flags */ csbe_datadef_value_int(csbe, 0); /* Reserved byte */ csbe_datadef_value_int(csbe, 0); /* Reserved uint16 (free space?) */ csbe_datadef_value_int(csbe, len); /* Length */ } csbe_datadef_value_bytearray(csbe, (unsigned char *)buffer, len); csbe_datadef_value_int(csbe, 0); csbe_datadef_initval_end(csbe); return 1; err_csbe: return interr_csbe(ctx, e, INTERR_EMIT_STRINGLIT2); } static int emit_stringliteral_expr(struct CSlul *ctx, struct Csbe *csbe, const struct ExprNode *expr) { const char *buffer; size_t len; assert(expr->exprtype == E_STRING); ZCHK(string_to_bytes(ctx, expr, &buffer, &len)); return emit_stringliteral_buffer(ctx, csbe, buffer, len); } static int generate_datadef(struct CSlul *ctx, struct Csbe *csbe, struct TopLevelIdent *tlident, struct CsbeLibrary *importlib) { enum CsbeErr e; const char *name; size_t namelen; CHECK_STRUCT(*tlident); CHECK_STRUCT(tlident->decl); csbe_datadef_start(csbe, tlident->id, IS_IDENT_IMPLEMENTED(&tlident->decl) ? 0 : CSBEDD_EXTERNAL); name = node_nameptr(&tlident->decl.ident); namelen = tlident->decl.ident.length; csbe_datadef_set_name(csbe, name, namelen); if (tlident->decl.type.type == T_ELMNTRY && tlident->decl.type.u.builtin == BT_String) { /* Strings are stored inline at the top level (normally it is a reference type) */ if (!tlident->decl.u.initval) { ZCHK(emit_inline_string_type(ctx, csbe, 0)); } else { ZCHK(emit_stringliteral_expr(ctx, csbe, tlident->decl.u.initval->root)); } return 1; } else { ZCHK(emit_type(ctx, csbe, &tlident->decl.type)); } if (tlident->decl.u.initval) { ECHK(csbe_datadef_initval_start(csbe)); ZCHK(emit_exprdata(ctx, csbe, tlident->decl.u.initval->root)); csbe_datadef_initval_end(csbe); } csbe_datadef_end(csbe); /* Import/export */ if (importlib) { assert(tlident->iface_decl == NULL); ECHK(csbe_import_decl(csbe, CSBEST_LAST_DATA, importlib, NULL, name, namelen)); } else if (tlident->iface_decl != NULL) { ECHK(csbe_export_decl(csbe, CSBEST_LAST_DATA, NULL, name, namelen)); } return 1; err_csbe: return interr_csbe(ctx, e, INTERR_EMIT_DATADEF); } static int generate_datadefs(struct CSlul *ctx, struct Csbe *csbe, struct TopLevels *tl, struct CsbeLibrary *importlib) { struct TopLevelIdent *tlident; for (tlident = tl->idents_list; tlident; tlident = tlident->next) { if (IS_FUNC_DECL(&tlident->decl)) continue; if (tlident->decl.type.type == T_IMPORTED) continue; if (importlib) { /* Skip "public constants" (with externally visible value). These generally get eliminated by constant-expr evaluation. FIXME this is NOT the case when taking the address, so there needs to be an exception for that case. (unless the data is copied into the module that imports the datadef) */ if (tlident->decl.u.initval) continue; } ZCHK(generate_datadef(ctx, csbe, tlident, importlib)); } return 1; } static int generate_all_datadefs(struct CSlul *ctx, struct Csbe *csbe) { struct CSlulDependency *dep; csbe_set_num_datadefs(csbe, ctx->num_datadefs + ctx->num_literals); ZCHK(generate_datadefs(ctx, csbe, &ctx->impl, NULL)); /* TODO these should be exported */ ZCHK(generate_datadefs(ctx, csbe, &ctx->module.iface, NULL)); for (dep = ctx->module.first_dep; dep; dep = dep->next) { ZCHK(generate_datadefs(ctx, csbe, &dep->module->iface, dep->importlib)); } return 1; } /* Func defs */ static int generate_funcdecl(struct CSlul *ctx, struct Csbe *csbe, struct TopLevelIdent *tlident, struct CsbeLibrary *importlib) { enum CsbeErr e; const struct Type *type; const struct FuncType *ft; struct FieldOrParamEntry *p; unsigned flags = 0, misc, is_void = 0; const char *name; size_t namelen; CHECK_STRUCT(*tlident); CHECK_STRUCT(tlident->decl); type = funcdecl_real_type(&tlident->decl.type); ft = type->u.func; misc = type->misc; if ((misc & M_NORETURN) != 0) { flags |= CSBEFD_NORETURN; is_void = 1; } else if ((misc & M_VOIDRETURN) != 0) { is_void = 1; } name = node_nameptr(&tlident->decl.ident); namelen = tlident->decl.ident.length; /* Make SlulApp.main() visible */ if (tlident->class_ && ident_is_appmain(tlident)) { flags |= CSBEFD_OBJGLOBAL | CSBEFD_MAIN; } /* TODO use CSBEAPI_ANYCALL for internal functions */ ECHK(csbe_funcdef_start(csbe, tlident->id, ft->params.count + ( tlident->class_ && !tlident->is_typeident ? 1 : 0), CSBEABI_C_NORMAL, flags)); /* Name */ if (tlident->class_) { /* "ClassName_funcname" */ const struct TreeNode *classident = &tlident->class_->ident; const char *clname = node_nameptr(classident); size_t clnamelen = classident->length; size_t bufflen = clnamelen + 1 + namelen + 1; char *buff = aalloc(ctx, bufflen, 1); if (!buff) return 0; memcpy(buff, clname, clnamelen); buff[clnamelen] = '_'; memcpy(buff+clnamelen+1, name, namelen); buff[bufflen-1] = '\0'; name = buff; namelen = bufflen - 1; } csbe_funcdef_set_name(csbe, name, namelen); /* Return type */ if (is_void) { csbe_type_void(csbe); } else { ZCHK(emit_type(ctx, csbe, &ft->returntype)); } /* Parameters */ if (tlident->class_ && !tlident->is_typeident) { csbe_type_simple(csbe, CSBET_DPTR); } for (p = ft->params.first; p; p = p->next) { ZCHK(emit_type(ctx, csbe, &p->f.vardef.decl.type)); } csbe_funcdef_end(csbe); /* Import/export */ if (importlib) { assert(tlident->iface_decl == NULL); ECHK(csbe_import_decl(csbe, CSBEST_LAST_FUNC, importlib, NULL, name, namelen)); } else if (tlident->iface_decl != NULL) { ECHK(csbe_export_decl(csbe, CSBEST_LAST_FUNC, NULL, name, namelen)); } return 1; err_csbe: return interr_csbe(ctx, e, INTERR_EMIT_FUNCDECL); } static int generate_funcdecls(struct CSlul *ctx, struct Csbe *csbe, struct TopLevels *tl, struct CsbeLibrary *importlib) { struct TopLevelIdent *tlident; for (tlident = tl->idents_list; tlident; tlident = tlident->next) { if (!IS_FUNC_DECL(&tlident->decl)) continue; if (tlident->decl.type.type == T_IMPORTED) continue; ZCHK(generate_funcdecl(ctx, csbe, tlident, importlib)); } return 1; } static int generate_all_funcdecls(struct CSlul *ctx, struct Csbe *csbe) { struct CSlulDependency *dep; csbe_set_num_funcdefs(csbe, ctx->num_funcdefs); ZCHK(generate_funcdecls(ctx, csbe, &ctx->impl, NULL)); ZCHK(generate_funcdecls(ctx, csbe, &ctx->module.iface, NULL)); for (dep = ctx->module.first_dep; dep; dep = dep->next) { ZCHK(generate_funcdecls(ctx, csbe, &dep->module->iface, dep->importlib)); } return 1; } struct NextTemp { unsigned varlane : 2; /**< Next varlane to use */ unsigned used : 3; /**< Bitmask of used varlanes */ }; #define BITS(b3,b2,b1) ((b1) | ((b2) << 1) | ((b3) << 2)) #define VARLANE_TO_BIT(vl) (1 << ((vl)-1)) static const struct NextTemp next_temp[] = { /* used -> next, used(3,2,1) */ /* 000 */ { 1, BITS(0,0,1) }, /* 001 */ { 2, BITS(0,1,1) }, /* 010 */ { 1, BITS(0,1,1) }, /* 011 */ { 3, BITS(1,1,1) }, /* 100 */ { 1, BITS(1,0,1) }, /* 101 */ { 2, BITS(1,1,1) }, /* 110 */ { 1, BITS(1,1,1) }, /* 111 */ { 0, 0 }, /* should never happen */ }; /* IR generation */ struct GenState { unsigned var_id; /**< ID of next variable */ unsigned ebb_id; /**< ID of next basic block */ unsigned used_temp_varlanes; /**< Bitmask of used varlanes for temporaries */ unsigned last_expr_in_tempvar; /**< Used by emit_expr/operand_from_expr */ struct LoopIRState *loopstate; /**< Stack of loopstates */ struct SwitchIRState *switchstate; /**< Stack of switchstates */ }; /** Returns the varlane of a temporary that was just produced */ static unsigned producetemp(struct GenState *gs) { struct NextTemp next = next_temp[gs->used_temp_varlanes]; assert(next.varlane != 0); gs->used_temp_varlanes = next.used; return next.varlane; } /** Marks the varlane of a temporary as available */ static void consumetemp(struct GenState *gs, struct TempVarInfo *tmpvar) { assert(tmpvar->varlane != 0); gs->used_temp_varlanes &= ~VARLANE_TO_BIT(tmpvar->varlane); } /* TODO trapping arithmetic also */ static const unsigned char op2csbe[CSBEO_LAST] = { /* OP_POS */ CSBEO_MOVE, /* OP_NEG */ CSBEO_NEG, /* OP_REFTO */ 0, /* OP_DEREF */ CSBEO_DEREF, /* OP_OPTIONAL */ CSBEO_MOVE, /* TODO what to do here? */ /* OP_ADD */ CSBEO_ADD, /* OP_SUB */ CSBEO_SUB, /* OP_MUL */ CSBEO_MUL, /* OP_DIV */ CSBEO_DIV, /* OP_MOD */ CSBEO_MOD, /* OP_NOT */ CSBEO_LNOT, /* OP_AND */ CSBEO_LAND, /* OP_OR */ CSBEO_LOR, /* OP_REF_IS */ CSBEO_EQ, /* OP_REF_IS_NOT */ CSBEO_NEQ, /* OP_EQ */ CSBEO_EQ, /* OP_NOTEQ */ CSBEO_NEQ, /* OP_LESS */ CSBEO_LT, /* OP_LEQ */ CSBEO_LE, /* OP_GREATER */ CSBEO_GT, /* OP_GEQ */ CSBEO_GE, /* OP_ASSIGN */ 0, /* TODO move these to a separate E_ASSIGN op? */ /* OP_ADDASSIGN */ 0, /* OP_SUBASSIGN */ 0, /* OP_MULASSIGN */ 0, /* OP_DIVASSIGN */ 0, /* OP_MEMBER */ 0, /* Ternary operators */ /* OP_CONDITIONAL */ CSBEO_CHOICE }; /* TODO should take pointer dereferencing/address-of levels into account */ static enum CsbeTypeKind get_simple_type(struct CSlul *ctx, const struct TypeRef *tr) { const struct Type *type = real_type_tr(ctx, tr); deeper: /* FIXME T_INTERNAL should be elimitated in the frontend */ if (type->type == T_INTERNAL) return CSBET_I64; /* FIXME hack */ else if (type->type == T_ENUM) { type = &type->u.enu->base; goto deeper; } else if (type->type == T_REF || type->type == T_SLOT) { return CSBET_DPTR; } else if (type->type == T_FUNC) { return CSBET_FPTR; } /* TODO add a CSBET_DPTR_OR_INT type? */ assert(type->type == T_ELMNTRY); return elementary2csbetype[type->u.builtin]; } #define FC_POINTER 0x1 #define FC_METHOD 0x2 #define FC_CONSTR 0x4 static unsigned classify_func(struct ExprNode *n) { assert(n->exprtype == E_CALL); if (n->b.expr->exprtype == E_IDENT) { const struct IdentDecl *ident = get_identexpr_decl(n->b.expr); return IS_FUNC_DECL(ident) ? 0 : FC_POINTER; } else if (n->b.expr->exprtype == E_TYPEIDENT) { return FC_CONSTR; } else { assert(n->b.expr->exprtype == E_FIELD); return FC_METHOD; } } static int is_using_temporary(struct CSlul *ctx, const struct ExprNode *n) { switch (n->exprtype) { case E_BOOL: case E_INTEGER: case E_FLOAT: /* TODO can it require a temporary? */ case E_NONE: case E_UNDEF: case E_THIS: return 0; case E_TYPEIDENT: case E_IDENT: { struct IdentDecl *decl = get_identexpr_decl(n); assert(!n->is_called); return !IS_IDENT_LOCAL(decl) || is_lvalue(ctx, n); } default: return 1; } } static void add_operand(struct CSlul *ctx, struct Csbe *csbe, struct ExprNode *n, struct GenState *gs) { uint64 value; CHECK_STRUCT(*n); switch (n->exprtype) { case E_BOOL: value = n->a.intval; break; case E_INTEGER: if (!INTLITERAL_IS_NEGATIVE(*n)) { value = n->a.intval; } else { value = -n->a.intval; } break; case E_FLOAT: /* TODO */ value = 0; break; case E_NONE: value = 0; break; case E_UNDEF: /* TODO what to do here? this should probably only be allowed in assignment statements */ value = 0; break; case E_THIS: csbe_operand_var_in(csbe, THIS_VAR_ID, 0); return; case E_TYPEIDENT: case E_IDENT: { struct IdentDecl *decl = get_identexpr_decl(n); if (!IS_IDENT_LOCAL(decl) || is_lvalue(ctx, n)) goto using_temporary; csbe_operand_var_in(csbe, ((struct VarDef *)decl)->var_id, 0); return; } default: using_temporary: assert(!gs->last_expr_in_tempvar); consumetemp(gs, &n->b.tempvar); csbe_operand_var_in(csbe, n->b.tempvar.var_id, CSBE_OPV_DISCARD); return; } /* Literal */ csbe_operand_immed(csbe, get_simple_type(ctx, &n->u.tr), value); } static void add_address_operand(struct Csbe *csbe, struct ExprNode *n, struct GenState *gs) { assert(!gs->last_expr_in_tempvar); CHECK_STRUCT(*n); consumetemp(gs, &n->b.tempvar); csbe_operand_var_in(csbe, n->b.tempvar.var_id, CSBE_OPV_DISCARD); } static int add_result(struct CSlul *ctx, struct Csbe *csbe, struct ExprNode *n, unsigned var_id, struct GenState *gs) { unsigned varlane = producetemp(gs); assert(varlane != 0); CHECK_STRUCT(*n); csbe_funcbody_vardef_start(csbe, 0, var_id, varlane); if (is_lvalue(ctx, n)) { csbe_type_simple(csbe, CSBET_DPTR); } else { ZCHK(emit_type_tr(ctx, csbe, &n->u.tr)); } csbe_funcbody_vardef_end(csbe); csbe_operand_var_out(csbe, var_id, 0); n->b.tempvar.var_id = var_id; n->b.tempvar.varlane = varlane; return 1; } /** * Dereferences any pointers in the given type. * * \param tr Type reference. * \param out Storage to use for return value if needed. * \return Pointer to the result. Can return tr itself. */ static struct TypeRef *deref_tr(struct CSlul *ctx, struct TypeRef *tr, struct TypeRef *out) { struct TypeRef *res = tr; struct TypeRef realtr; do { struct Type *real = real_tr(ctx, res, &realtr); if (real->type != T_REF) return res; assert(real->type != T_SLOT); assert(real->type != T_GENERICSPEC); res = out; *res = nested_tr_const(real->u.nested, &realtr); } while (1); } static int add_type_operand(struct CSlul *ctx, struct Csbe *csbe, struct TypeRef *tr) { struct TypeRef deeptr; enum CsbeErr e; tr = deref_tr(ctx, tr, &deeptr); ECHK(csbe_operand_type_start(csbe)); ZCHK(emit_type_tr(ctx, csbe, tr)); csbe_operand_type_end(csbe); return 1; err_csbe: return interr_csbe(ctx, e, INTERR_EMIT_EXPR); } /** * Allocates a temporary variable for the IR generator. * This is used for array indexes. */ static void alloc_irtemp(struct Csbe *csbe, enum CsbeTypeKind simpletype, unsigned flags, unsigned var_id) { csbe_funcbody_vardef_start(csbe, flags, var_id, CSBE_VARLANE_NONE); csbe_type_simple(csbe, simpletype); csbe_funcbody_vardef_end(csbe); } /* This prevents an assertion failure when no value has been produced. TODO remove when the functionality is implemented correctly. */ static int make_dummy_value(struct CSlul *ctx, struct Csbe *csbe, struct ExprNode *n, unsigned var_num) { enum CsbeErr e; csbe_funcbody_vardef_start(csbe, 0, var_num, CSBE_VARLANE_NONE); ZCHK(emit_type_tr(ctx, csbe, &n->u.tr)); csbe_funcbody_vardef_end(csbe); ECHK(csbe_op_start(csbe, CSBEO_MOVE)); csbe_operand_immed(csbe, CSBET_DPTR, 123); csbe_operand_var_out(csbe, var_num, 0); csbe_op_end(csbe); n->b.tempvar.var_id = var_num; return 1; err_csbe: return interr_csbe(ctx, e, INTERR_EMIT_EXPR); } #define DISCARD_RESULT 0x1 static int emit_assign_op(struct CSlul *ctx, struct Csbe *csbe, struct ExprNode *n, unsigned flags, unsigned *var_produced, struct GenState *gs) { enum CsbeErr e; unsigned dest_varnum; int is_result_used = (n->rpnnext || (flags & DISCARD_RESULT) == 0); int is_dest_localvar = !is_lvalue_ex(ctx, n->a.expr, &dest_varnum); int source_is_temp = is_using_temporary(ctx, n->b.expr); ECHK(csbe_op_start(csbe, is_dest_localvar ? CSBEO_MOVE : CSBEO_MOVETOPTR)); /* Source (n->b) */ if (source_is_temp && is_result_used) { /* Don't discard the temporary yet */ csbe_operand_var_in(csbe, n->b.expr->b.tempvar.var_id, 0); /* TODO workaround until E_ARRAY etc. are implemented */ } else if (n->b.expr->exprtype == E_ARRAY || n->b.expr->exprtype == E_STRUCT || n->b.expr->exprtype == E_STRING) { csbe_operand_var_in(csbe, n->b.expr->b.tempvar.var_id, 0); } else { add_operand(ctx, csbe, n->b.expr, gs); } /* Destination (n->a) */ if (is_dest_localvar) { csbe_operand_var_out(csbe, dest_varnum, 0); } else { add_address_operand(csbe, n->a.expr, gs); } csbe_op_end(csbe); /* Result (= copy of source) */ if (is_result_used) { if (source_is_temp) { n->b.tempvar = n->b.expr->b.tempvar; } else { ECHK(csbe_op_start(csbe, CSBEO_MOVE)); add_operand(ctx, csbe, n->b.expr, gs); ZCHK(add_result(ctx, csbe, n, (*var_produced)++, gs)); csbe_op_end(csbe); } } return 1; err_csbe: return interr_csbe(ctx, e, INTERR_EMIT_ASSIGN); } static int emit_opassign_op(struct CSlul *ctx, struct Csbe *csbe, struct ExprNode *n, unsigned *var_produced, struct GenState *gs) { enum CsbeErr e; unsigned dest_varnum = -1; int is_dest_localvar = !is_lvalue_ex(ctx, n->a.expr, &dest_varnum); enum CsbeOp op; switch ((int)n->op) { /* TODO trapping operations */ case OP_ADDASSIGN: op = CSBEO_ADD; break; case OP_SUBASSIGN: op = CSBEO_SUB; break; case OP_MULASSIGN: op = CSBEO_MUL; break; case OP_DIVASSIGN: op = CSBEO_DIV; break; default: assert(0); } assert(n->rpnnext == NULL); if (!is_dest_localvar) { /* Load */ dest_varnum = (*var_produced)++; csbe_funcbody_vardef_start(csbe, CSBEVD_SIMPLE, dest_varnum, CSBE_VARLANE_NONE); ZCHK(emit_type_tr(ctx, csbe, &n->u.tr)); csbe_funcbody_vardef_end(csbe); ECHK(csbe_op_start(csbe, CSBEO_DEREF)); csbe_operand_var_in(csbe, n->a.expr->b.tempvar.var_id, 0); csbe_operand_var_out(csbe, dest_varnum, 0); csbe_op_end(csbe); } assert(dest_varnum != (unsigned)-1); /* Perform operation */ ECHK(csbe_op_start(csbe, op)); csbe_operand_var_in(csbe, dest_varnum, CSBE_OPV_DISCARD); add_operand(ctx, csbe, n->b.expr, gs); csbe_operand_var_out(csbe, dest_varnum, 0); csbe_op_end(csbe); if (!is_dest_localvar) { /* Write back */ ECHK(csbe_op_start(csbe, CSBEO_MOVETOPTR)); csbe_operand_var_in(csbe, dest_varnum, CSBE_OPV_DISCARD); csbe_operand_var_in(csbe, n->a.expr->b.tempvar.var_id, CSBE_OPV_DISCARD); csbe_op_end(csbe); } return 1; err_csbe: return interr_csbe(ctx, e, INTERR_EMIT_OPASSIGN); } /** * Emits an expression. The result can be obtained as an operand * using operand_from_expr(). * * \param ctx Compilation context * \param csbe CSBE context * \param expr The expression * \param flags Bitmask of flags (flags: DISCARD_RESULT) * \param gs Code generator state */ static int emit_expr(struct CSlul *ctx, struct Csbe *csbe, struct ExprRoot *expr, unsigned flags, struct GenState *gs) { struct ExprNode *n = expr->rpn; unsigned var_produced = gs->var_id; enum CsbeErr e; gs->used_temp_varlanes = 0; /* TODO save uncomsumed temporaries when doing a call? or should the backend do this? - yes, the backend can to this, because temporaries are "consume once", so it should be trivial to check. */ /* TODO automatic referencing/dereferencing */ gs->last_expr_in_tempvar = 0; assert(n); for (; n; n = n->rpnnext) { switch (n->exprtype) { case E_UNARYOP: { unsigned op = op2csbe[n->op]; /* TODO add check for OP_OPTIONAL */ if (op == CSBEO_DEREF && n->is_assigned) { ECHK(csbe_op_start(csbe, CSBEO_MOVE)); add_operand(ctx, csbe, n->a.expr, gs); ZCHK(add_result(ctx, csbe, n, var_produced++, gs)); csbe_op_end(csbe); } else if (op) { ECHK(csbe_op_start(csbe, op)); add_operand(ctx, csbe, n->a.expr, gs); ZCHK(add_result(ctx, csbe, n, var_produced++, gs)); csbe_op_end(csbe); } else if (n->op == OP_REFTO) { unsigned varnum; if (!is_lvalue_ex(ctx, n->a.expr, &varnum)) { ECHK(csbe_op_start(csbe, CSBEO_ADDRLOCAL)); csbe_operand_var_in(csbe, varnum, 0); ZCHK(add_result(ctx, csbe, n, var_produced++, gs)); csbe_op_end(csbe); } else { /* The address is already in the next tempvar. This MOVE should get optimized out (merged) in the backend. */ ECHK(csbe_op_start(csbe, CSBEO_MOVE)); add_address_operand(csbe, n->a.expr, gs); ZCHK(add_result(ctx, csbe, n, var_produced++, gs)); csbe_op_end(csbe); } } break; } case E_BINARYOP: if (n->op == OP_ASSIGN) { ZCHK(emit_assign_op(ctx, csbe, n, flags, &var_produced, gs)); /* XXX check that the source temp gets discarded at the end of this function */ } else if (n->op >= OP_ADDASSIGN && n->op <= OP_DIVASSIGN) { assert((flags & DISCARD_RESULT) != 0); ZCHK(emit_opassign_op(ctx, csbe, n, &var_produced, gs)); flags &= ~DISCARD_RESULT; /* TODO do short-circuiting for CSBEO_LAND if: - the right-hand subexpr has side-effects, or - the right-hand subexpr involves a complex operation (field/index/call/deref/...) including implicit deref (or perhaps always do it for simplicity?) */ } else { /* TODO special handling for OP_EQ etc. with IT_UnsizedInt operand (but only if needed?) XXX can we really do that? the operands will come before! - or just promote all IT_UnsignedInt to int64 (if results < 0 are possible) or uint64 (if not). - or simplify the language itself? - evaluate constants as "int65" - both sides constant = eliminated by frontend. - both sides of known type = OK - one side of known type, other of constant type = OK - (maybe:) if all terms have a known sign: - move negative terms to the other side a + b - c > d - 10 a + b + 10 > d + c if the left side overflows: true if the right side overflows: false XXX what to do if both sides can overflow? - otherwise the frontend should report an error (which can be fixed by writing "... as XintXX") */ ECHK(csbe_op_start(csbe, op2csbe[n->op])); add_operand(ctx, csbe, n->a.expr, gs); add_operand(ctx, csbe, n->b.expr, gs); ZCHK(add_result(ctx, csbe, n, var_produced++, gs)); csbe_op_end(csbe); } break; case E_CONDITIONAL: /* TODO */ break; case E_CONDCHOICES: /* TODO */ break; case E_STRING: { unsigned datadef_id = ctx->num_datadefs + ctx->next_ir_literal++; /* Create global data item */ csbe_datadef_start(csbe, datadef_id, CSBEDD_NON_UNIQUE); ZCHK(emit_stringliteral_expr(ctx, csbe, n)); csbe_datadef_end(csbe); /* Add reference to global */ ECHK(csbe_op_start(csbe, CSBEO_ADDRGLOBAL)); csbe_operand_datadef(csbe, datadef_id); ZCHK(add_result(ctx, csbe, n, var_produced++, gs)); csbe_op_end(csbe); break; } case E_ARRAY: case E_STRUCT: { if (IS_COMPOUNDEXPR_NESTEDCONST(n)) { /* Do nothing here. Only the root level should be processed */ } else if (IS_COMPOUNDEXPR_CONST(n)) { unsigned datadef_id = ctx->num_datadefs + ctx->next_ir_literal++; /* Compile-time constant. Allocate in .rodata section */ csbe_datadef_start(csbe, datadef_id, CSBEDD_NON_UNIQUE); ZCHK(emit_type(ctx, csbe, n->u.tr.type)); ECHK(csbe_datadef_initval_start(csbe)); ZCHK(emit_exprdata(ctx, csbe, n)); csbe_datadef_initval_end(csbe); csbe_datadef_end(csbe); /* FIXME should implicitly dereference nested refs! */ /* Get address to global */ ECHK(csbe_op_start(csbe, is_lvalue(ctx, n) ? CSBEO_ADDRGLOBAL : CSBEO_LOADGLOBAL)); csbe_operand_datadef(csbe, datadef_id); ZCHK(add_result(ctx, csbe, n, var_produced++, gs)); csbe_op_end(csbe); } else { /* TODO allocate a temporary variable on stack. initialize it with the contents at runtime. use ADDRLOCAL to get a reference */ ZCHK(make_dummy_value(ctx, csbe, n, var_produced++)); } break; } case E_FIELDINIT: /* Handled in E_CALL and in E_STRUCT. But the nodes come in RPN order, so these come first. */ break; case E_INDEX: { size_t left = n->a.exprlist->length; struct ExprNode **indexptr = &n->a.exprlist->elems[0]; struct ExprNode *arrayexpr = n->b.expr; struct DimensionInfo *dim; uint64 offset = 0; int is_dynamic = 0; ZCHK(get_dimension_infos(ctx, arrayexpr, left)); /* A[a,b,c] -> A[a*al + b*bl + c*cl] */ dim = ctx->ir_dims_buffer; while (left > 0) { struct ExprNode *index = *indexptr; if (index->exprtype == E_INTEGER) { offset += index->a.intval * dim->elemlen; if (UNLIKELY(ctx->has_fatal_errors)) return 0; } else { is_dynamic = 1; } left--; indexptr++; dim++; } if (!is_dynamic) { /* FIXME should implicitly dereference nested refs! */ ECHK(csbe_op_start(csbe, is_lvalue(ctx, n) ? CSBEO_ADDRELEM : CSBEO_LOADELEM)); add_type_operand(ctx, csbe, &arrayexpr->u.tr); add_operand(ctx, csbe, arrayexpr, gs); csbe_operand_immed(csbe, CSBET_USIZE, offset); csbe_operand_index(csbe, 0); ZCHK(add_result(ctx, csbe, n, var_produced++, gs)); csbe_op_end(csbe); } else { /* The index is computed at runtime */ int is_first = 1; unsigned tempvar_add = var_produced++; unsigned tempvar_sum = var_produced++; alloc_irtemp(csbe, CSBET_USIZE, CSBEVD_MULTI_READ|CSBEVD_MULTI_WRITE, tempvar_add); alloc_irtemp(csbe, CSBET_USIZE, CSBEVD_MULTI_READ|CSBEVD_MULTI_WRITE, tempvar_sum); left = n->a.exprlist->length; dim = ctx->ir_dims_buffer; indexptr = &n->a.exprlist->elems[0]; while (left > 0) { struct ExprNode *index = *indexptr; if (index->exprtype != E_INTEGER) { /* TODO add bounds check for risky (not properly constrained) lengths */ ECHK(csbe_op_start(csbe, CSBEO_MUL_TRAP)); add_operand(ctx, csbe, index, gs); csbe_operand_immed(csbe, CSBET_USIZE, dim->elemlen); if (UNLIKELY(ctx->has_fatal_errors)) return 0; csbe_operand_var_out(csbe, tempvar_add, 0); csbe_op_end(csbe); ECHK(csbe_op_start(csbe, CSBEO_ADD_TRAP)); if (is_first) { csbe_operand_immed(csbe, CSBET_USIZE, offset); is_first = 0; } else { csbe_operand_var_in(csbe, tempvar_sum, 0); } csbe_operand_var_in(csbe, tempvar_add, CSBE_OPV_DISCARD); csbe_operand_var_out(csbe, tempvar_sum, 0); csbe_op_end(csbe); } left--; indexptr++; dim++; } /* FIXME should implicitly dereference nested refs! */ ECHK(csbe_op_start(csbe, is_lvalue(ctx, n) ? CSBEO_ADDRELEM : CSBEO_LOADELEM)); add_type_operand(ctx, csbe, &arrayexpr->u.tr); add_operand(ctx, csbe, arrayexpr, gs); csbe_operand_var_in(csbe, tempvar_sum, CSBE_OPV_DISCARD); csbe_operand_index(csbe, 0); ZCHK(add_result(ctx, csbe, n, var_produced++, gs)); csbe_op_end(csbe); } break; } case E_FIELD: if (n->u.tr.type) { /* not a method call */ /* FIXME should implicitly dereference nested refs! */ ECHK(csbe_op_start(csbe, is_lvalue(ctx, n) ? CSBEO_ADDRELEM : CSBEO_LOADELEM)); add_type_operand(ctx, csbe, &n->a.expr->u.tr); add_operand(ctx, csbe, n->a.expr, gs); csbe_operand_immed(csbe, CSBET_USIZE, 0); csbe_operand_index(csbe, n->b.bound_field->vardef.var_id); ZCHK(add_result(ctx, csbe, n, var_produced++, gs)); csbe_op_end(csbe); } break; case E_CALL: { unsigned argleft; unsigned classif; struct ExprNode **argptr; classif = classify_func(n); csbe_call_start(csbe, ((classif & FC_METHOD) != 0 ? 1 : 0) + n->a.exprlist->length); /* this pointer */ if ((classif & FC_METHOD) != 0) { ECHK(csbe_call_arg_start(csbe)); add_operand(ctx, csbe, n->b.expr->a.expr, gs); csbe_call_arg_end(csbe); } /* Arguments */ argptr = n->a.exprlist->elems; for (argleft = n->a.exprlist->length; argleft--; argptr++) { struct ExprNode *arg = *argptr; if (arg->exprtype == E_FIELDINIT) { arg = arg->a.expr; } ECHK(csbe_call_arg_start(csbe)); add_operand(ctx, csbe, arg, gs); csbe_call_arg_end(csbe); } if ((classif & FC_POINTER) == 0) { /* Function definition */ struct TopLevelIdent *tlident; ECHK(csbe_op_start(csbe, CSBEO_CALL_FUNCDEF)); csbe_operand_callinfo(csbe); csbe_operand_enum(csbe, CSBECM_NORMAL); if ((classif & FC_METHOD) != 0) { tlident = (struct TopLevelIdent *)n->b.expr->b.bound_method; } else { tlident = (struct TopLevelIdent *)n->b.expr->a.ident; } csbe_operand_funcdef(csbe, tlident->id); } else { /* Function pointer */ /* FIXME should implicitly dereference nested refs! (note that function refs != data refs) */ ECHK(csbe_op_start(csbe, CSBEO_CALL_FUNCVAR)); csbe_operand_callinfo(csbe); csbe_operand_enum(csbe, CSBECM_NORMAL); ZCHK(add_type_operand(ctx, csbe, &n->u.tr)); add_operand(ctx, csbe, n->b.expr, gs); } csbe_op_end(csbe); /* Result */ if (n->rpnnext || (flags & DISCARD_RESULT) == 0) { ECHK(csbe_op_start(csbe, CSBEO_CALL_GET_RETURN)); ZCHK(add_result(ctx, csbe, n, var_produced++, gs)); csbe_op_end(csbe); } else { ECHK(csbe_op_start(csbe, CSBEO_CALL_DISCARD_RETURN)); csbe_op_end(csbe); } break; } case E_IDENT: case E_TYPEIDENT: { struct IdentDecl *decl = get_identexpr_decl(n); if (n->is_called) { /* do nothing here */ break; } else if (!IS_IDENT_LOCAL(decl)) { struct TopLevelIdent *tlident = (struct TopLevelIdent *)decl; assert(!n->is_assigned); if (!IS_FUNC_DECL(&tlident->decl)) { ECHK(csbe_op_start(csbe, is_lvalue(ctx, n) ? /* TODO do not deref strings here! */ CSBEO_ADDRGLOBAL : CSBEO_LOADGLOBAL)); csbe_operand_datadef(csbe, tlident->id); ZCHK(add_result(ctx, csbe, n, var_produced++, gs)); csbe_op_end(csbe); } else { ECHK(csbe_op_start(csbe, CSBEO_ADDRFUNC)); csbe_operand_funcdef(csbe, tlident->id); ZCHK(add_result(ctx, csbe, n, var_produced++, gs)); csbe_op_end(csbe); } break; } else if (is_lvalue(ctx, n)) { struct VarDef *vardef = (struct VarDef *)decl; ECHK(csbe_op_start(csbe, CSBEO_ADDRLOCAL)); csbe_operand_var_in(csbe, vardef->var_id, 0); ZCHK(add_result(ctx, csbe, n, var_produced++, gs)); csbe_op_end(csbe); break; } /* else: not using a temporary */ break; } case E_BOOL: case E_INTEGER: case E_FLOAT: case E_NONE: case E_UNDEF: case E_THIS: /* No temporaries nor any operations are generated for literals of elementary types. So nothing is done here. Instead, the AST is checked when the operator is reached and if the operand is a literal, then it is emitted using csbe_operand_immed() rather than consuming a temporary. */ break; default: internal_error(ctx, INTERR_EMIT_BADEXPR); break; } } if (var_produced != gs->var_id) { gs->var_id = var_produced; gs->last_expr_in_tempvar = 1; /* XXX is this correct for OP_ASSIGN? */ if ((flags & DISCARD_RESULT) != 0) { ECHK(csbe_op_start(csbe, CSBEO_DISCARD)); csbe_operand_var_in(csbe, var_produced-1, CSBE_OPV_DISCARD); csbe_op_end(csbe); } } return 1; err_csbe: return interr_csbe(ctx, e, INTERR_EMIT_EXPR); } /** Uses the result of emit_expr as an operand */ static void operand_from_expr(struct CSlul *ctx, struct Csbe *csbe, struct ExprRoot *expr, struct GenState *gs) { if (gs->last_expr_in_tempvar) { csbe_operand_var_in(csbe, gs->var_id-1, CSBE_OPV_DISCARD); } else { add_operand(ctx, csbe, expr->root, gs); } } /** * Emits a conditional jump. * * \param ctx Compilation context * \param csbe CSBE context * \param expr The condition expression * \param ebb_id Target ebb_id if jump condition is met * \param branch_type Whether to branch if zero, if non-zero, etc. * \param branch_balance Whether a branch or non-branch is more common. * \param gs Code generator state */ static int emit_condjump(struct CSlul *ctx, struct Csbe *csbe, struct ExprRoot *expr, unsigned ebb_id, enum CsbeBranchType branch_type, enum CsbeBranchBalance branch_balance, struct GenState *gs) { enum CsbeErr e; ZCHK(emit_expr(ctx, csbe, expr, 0, gs)); ECHK(csbe_op_start(csbe, CSBEO_CONDJUMP)); csbe_operand_ebb(csbe, ebb_id); operand_from_expr(ctx, csbe, expr, gs); csbe_operand_enum(csbe, branch_type); csbe_operand_enum(csbe, branch_balance); csbe_op_end(csbe); return 1; err_csbe: return interr_csbe(ctx, e, INTERR_EMIT_CONDJUMP); } /** * Emits a jump-if-compare(expr_a, var_b). * * \param ctx Compilation context * \param csbe CSBE context * \param a_expr The condition expression * \param b_var Variable to compare with * \param b_flags Flags for variable access (can be CSBE_OPV_DISCARD) * \param ebb_id Target ebb_id if jump condition is met * \param branch_type Whether to branch if equal(Z), if inequal(NZ), etc. * \param branch_balance Whether a branch or non-branch is more common. * \param gs Code generator state */ static int emit_comparejump(struct CSlul *ctx, struct Csbe *csbe, unsigned a_var, unsigned a_flags, struct ExprRoot *b_expr, unsigned ebb_id, enum CsbeBranchType branch_type, enum CsbeBranchBalance branch_balance, struct GenState *gs) { enum CsbeErr e; ZCHK(emit_expr(ctx, csbe, b_expr, 0, gs)); ECHK(csbe_op_start(csbe, CSBEO_COMPAREJUMP)); csbe_operand_ebb(csbe, ebb_id); csbe_operand_var_in(csbe, a_var, a_flags); operand_from_expr(ctx, csbe, b_expr, gs); csbe_operand_enum(csbe, branch_type); csbe_operand_enum(csbe, branch_balance); csbe_op_end(csbe); return 1; err_csbe: return interr_csbe(ctx, e, INTERR_EMIT_COMPAREJUMP); } /** * Emits an unconditional jump. * * \param ctx Compilation context * \param csbe CSBE context * \param ebb_id Target ebb_id if jump condition is met */ static int emit_jump(struct CSlul *ctx, struct Csbe *csbe, unsigned ebb_id) { enum CsbeErr e; ECHK(csbe_op_start(csbe, CSBEO_JUMP)); csbe_operand_ebb(csbe, ebb_id); csbe_op_end(csbe); return 1; err_csbe: return interr_csbe(ctx, e, INTERR_EMIT_JUMP); } struct LoopIRState { struct LoopIRState *saved_ls; unsigned cond_ebb, body_ebb, empty_ebb, end_ebb, break_ebb; }; struct SwitchIRState { struct SwitchIRState *saved_ss; unsigned switchvar; }; /** * Converts a goto_id to an ebb_id (basic block ID). * Goto targets use the very last EBB IDs. * * \param s Statement * \param fb Enclosing function body */ #define GOTO_TO_EBB_ID(s, fb) ((fb)->num_ebb - (s)->u.gotoident->goto_id - 1) static int gen_stmtblock(struct CSlul *ctx, struct Csbe *csbe, struct FuncBody *fb, struct StmtBlock *sb, struct GenState *gs) { struct Stmt *s; enum CsbeErr e; for (s = &sb->stmt; s; s = s->next) { switch (s->type) { case S_BLOCK: ZCHK(gen_stmtblock(ctx, csbe, fb, s->u.block, gs)); break; case S_DECL: { unsigned flags = CSBEVD_MULTI_READ; if ((s->u.vardef->decl.type.quals & (Q_VAR|Q_WRONLY)) != 0) { flags |= CSBEVD_MULTI_WRITE; } csbe_funcbody_vardef_start(csbe, flags, s->u.vardef->var_id, CSBE_VARLANE_NONE); ZCHK(emit_type(ctx, csbe, &s->u.vardef->decl.type)); csbe_funcbody_vardef_end(csbe); if (s->u.vardef->decl.u.initval) { ZCHK(emit_expr(ctx, csbe, s->u.vardef->decl.u.initval, 0, gs)); if (gs->last_expr_in_tempvar) { csbe_divert_result(csbe, s->u.vardef->var_id, 0); } else { /* This is a literal value. Use a MOVE operation */ ECHK(csbe_op_start(csbe, CSBEO_MOVE)); operand_from_expr(ctx, csbe, s->u.vardef->decl.u.initval, gs); csbe_operand_var_out(csbe, s->u.vardef->var_id, 0); csbe_op_end(csbe); } } break; } case S_EXPR: ZCHK(emit_expr(ctx, csbe, s->u.expr, DISCARD_RESULT, gs)); break; case S_RETURN: if (s->u.expr) { ZCHK(emit_expr(ctx, csbe, s->u.expr, 0, gs)); ECHK(csbe_op_start(csbe, CSBEO_RETURN_ARG)); operand_from_expr(ctx, csbe, s->u.expr, gs); csbe_op_end(csbe); } else { ECHK(csbe_op_start(csbe, CSBEO_RETURN_VOID)); csbe_op_end(csbe); } break; case S_BREAK: assert(gs->loopstate != NULL); ZCHK(emit_jump(ctx, csbe, gs->loopstate->break_ebb)); break; case S_CONT: assert(gs->loopstate != NULL); ZCHK(emit_jump(ctx, csbe, gs->loopstate->cond_ebb)); break; case S_GOTO: ZCHK(emit_jump(ctx, csbe, GOTO_TO_EBB_ID(s, fb))); break; case S_IF: { unsigned true_ebb = gs->ebb_id++; unsigned false_ebb, end_ebb; if (s->u.ifstm->false_block) { false_ebb = gs->ebb_id++; end_ebb = gs->ebb_id++; } else { false_ebb = end_ebb = gs->ebb_id++; } /* Condition */ ZCHK(emit_condjump(ctx, csbe, s->u.ifstm->cond, false_ebb, CSBEBT_Z, CSBEBB_UNKNOWN, gs)); /* True block */ ECHK(csbe_ebb_start(csbe, true_ebb, CSBEEF_FLOWINTO)); ZCHK(gen_stmtblock(ctx, csbe, fb, &s->u.ifstm->true_block, gs)); if (s->u.ifstm->false_block) { /* End of true block */ ZCHK(emit_jump(ctx, csbe, end_ebb)); /* Start of false block */ ECHK(csbe_ebb_start(csbe, false_ebb, CSBEEF_DEFAULT)); ZCHK(gen_stmtblock(ctx, csbe, fb, s->u.ifstm->false_block, gs)); } ECHK(csbe_ebb_start(csbe, end_ebb, CSBEEF_FLOWINTO)); break; } case S_WHILE: case S_DOWHILE: case S_FOR: { struct LoopInfo *li = s->u.loopinfo; struct LoopIRState ls; ls.saved_ls = gs->loopstate; ls.cond_ebb = gs->ebb_id++; ls.body_ebb = gs->ebb_id++; if (li->empty_block) { ls.empty_ebb = gs->ebb_id++; } ls.end_ebb = gs->ebb_id; if (li->end_block) { gs->ebb_id++; } ls.break_ebb = gs->ebb_id++; gs->loopstate = &ls; if (s->type != S_FOR) { /* while/do-while */ if (s->type == S_WHILE) { if (is_bool_true(s->u.whilestm->cond)) { goto loop_body; } if (li->empty_block) { ZCHK(emit_condjump(ctx, csbe, s->u.whilestm->cond, ls.empty_ebb, CSBEBT_Z, CSBEBB_UNKNOWN, gs)); ZCHK(emit_jump(ctx, csbe, ls.body_ebb)); } ZCHK(emit_jump(ctx, csbe, ls.cond_ebb)); } else { assert(s->type == S_DOWHILE); assert(!li->empty_block); } /* Loop body */ loop_body: ECHK(csbe_ebb_start(csbe, ls.body_ebb, CSBEEF_FLOWINTO)); ZCHK(gen_stmtblock(ctx, csbe, fb, &li->block, gs)); /* Loop condition */ if (!is_bool_true(s->u.whilestm->cond)) { ECHK(csbe_ebb_start(csbe, ls.cond_ebb, CSBEEF_FLOWINTO)); ZCHK(emit_condjump(ctx, csbe, s->u.whilestm->cond, ls.body_ebb, CSBEBT_NZ, CSBEBB_SOMEWHAT_LIKELY, gs)); if (li->end_block) { ZCHK(emit_jump(ctx, csbe, ls.end_ebb)); } } else { /* while true */ ZCHK(emit_jump(ctx, csbe, ls.body_ebb)); } } else { /* For loop */ /* TODO */ } gs->loopstate = ls.saved_ls; /* End of loop. loopempty/loopend blocks follow */ if (li->empty_block) { ECHK(csbe_ebb_start(csbe, ls.empty_ebb, CSBEEF_DEFAULT)); ZCHK(gen_stmtblock(ctx, csbe, fb, li->empty_block, gs)); ZCHK(emit_jump(ctx, csbe, ls.end_ebb)); } if (li->end_block) { ECHK(csbe_ebb_start(csbe, ls.end_ebb, CSBEEF_DEFAULT)); ZCHK(gen_stmtblock(ctx, csbe, fb, li->end_block, gs)); } ECHK(csbe_ebb_start(csbe, ls.break_ebb, CSBEEF_FLOWINTO)); break; } case S_SWITCH: { struct SwitchIRState ss; unsigned first_ebb, case_ebb, default_ebb, end_ebb; struct SwitchCase *cas; ZCHK(emit_expr(ctx, csbe, s->u.switchstm->cond, 0, gs)); if (!gs->last_expr_in_tempvar) { /* switch on a variable or literal */ /* TODO for switch on variables, we should just set ss.switchvar to the variable id (as long as it is not a stack-allocated variable) */ ECHK(csbe_op_start(csbe, CSBEO_MOVE)); operand_from_expr(ctx, csbe, s->u.switchstm->cond, gs); /* TODO could use a varlane if there are no subcases */ csbe_funcbody_vardef_start(csbe, 0, gs->var_id, /*FIRST_VARLANE*/CSBE_VARLANE_NONE); ZCHK(emit_type_tr(ctx, csbe, &s->u.switchstm->cond->root->u.tr)); csbe_funcbody_vardef_end(csbe); csbe_operand_var_out(csbe, gs->var_id++, 0); csbe_op_end(csbe); } ss.switchvar = gs->var_id-1; ss.saved_ss = gs->switchstate; first_ebb = gs->ebb_id; /* TODO for strings etc it is probably better to use a "binary jump tree" (and low-level comparison operations won't work anyway) */ /* Jump sequence (sub,jz for each case value) */ case_ebb = first_ebb; default_ebb = CSBE_INVALID_EBB; for (cas = s->u.switchstm->cases; cas; cas = cas->next) { struct CaseValue *cv = &cas->values; unsigned varflags = cas->has_subcases ? 0 : CSBE_OPV_DISCARD; for (; cv; cv = cv->next) { if (cv->expr) { ZCHK(emit_comparejump(ctx, csbe, ss.switchvar, varflags, cv->expr, case_ebb, CSBEBT_Z, CSBEBB_SOMEWHAT_UNLIKELY, gs)); } else { default_ebb = case_ebb; } } case_ebb++; } end_ebb = case_ebb; gs->ebb_id = end_ebb+1; if (!s->u.switchstm->has_default) { default_ebb = end_ebb; } else { assert(default_ebb != CSBE_INVALID_EBB); } ZCHK(emit_jump(ctx, csbe, default_ebb)); /* Case EBB's */ case_ebb = first_ebb; for (cas = s->u.switchstm->cases; cas; cas = cas->next) { ECHK(csbe_ebb_start(csbe, case_ebb, CSBEEF_DEFAULT)); ZCHK(gen_stmtblock(ctx, csbe, fb, &cas->block, gs)); ZCHK(emit_jump(ctx, csbe, end_ebb)); case_ebb++; } gs->switchstate = ss.saved_ss; ECHK(csbe_ebb_start(csbe, end_ebb, CSBEEF_DEFAULT)); break; } case S_ASSERT: ZCHK(emit_expr(ctx, csbe, s->u.expr, 0, gs)); ECHK(csbe_op_start(csbe, CSBEO_CONDTRAP)); operand_from_expr(ctx, csbe, s->u.expr, gs); csbe_operand_enum(csbe, CSBEBT_Z); csbe_op_end(csbe); break; case S_TARGET: csbe_ebb_start(csbe, GOTO_TO_EBB_ID(s, fb), CSBEEF_FLOWINTO); break; case S_SUBCASE: { unsigned end_ebb = gs->ebb_id++; assert(gs->switchstate != NULL); ZCHK(emit_comparejump(ctx, csbe, gs->switchstate->switchvar, 0, s->u.subcase->value, end_ebb, CSBEBT_NZ, CSBEBB_UNKNOWN, gs)); ZCHK(gen_stmtblock(ctx, csbe, fb, &s->u.subcase->block, gs)); ECHK(csbe_ebb_start(csbe, end_ebb, CSBEEF_FLOWINTO)); break; } case S_UNREACH: ECHK(csbe_op_start(csbe, CSBEO_TRAP)); csbe_op_end(csbe); break; case S_NOP: break; } } return 1; err_csbe: gs->loopstate = NULL; /* ensure it doesn't point to stack */ return interr_csbe(ctx, e, INTERR_EMIT_STMT); } static int generate_func_ir(struct CSlul *ctx, struct Csbe *csbe, struct TopLevelIdent *tlident) { struct FuncBody *fb = tlident->decl.u.funcbody; enum CsbeErr e; unsigned i; const struct Type *type; const struct FieldOrParamList *params; const struct FieldOrParamEntry *par; struct GenState gs; assert(fb != NULL); /* TODO use separate allocator? or remove support for separate allocation? */ ECHK(csbe_funcbody_start(csbe, tlident->id, NULL, NULL, fb->num_ebb, fb->num_variables + fb->num_temporaries)); i = 0; if (tlident->class_ && !tlident->is_typeident) { /* Implicit "this" parameter */ csbe_funcbody_paramdef(csbe, CSBEVD_MULTI_READ, i); i++; } type = funcdecl_real_type(&tlident->decl.type); params = &type->u.func->params; for (par = params->first; par; par = par->next) { csbe_funcbody_paramdef(csbe, CSBEVD_MULTI_READ, i); i++; } gs.var_id = fb->num_variables; gs.ebb_id = 1; gs.loopstate = NULL; gs.switchstate = NULL; ECHK(csbe_ebb_start(csbe, 0, CSBEEF_DEFAULT)); ZCHK(gen_stmtblock(ctx, csbe, fb, &fb->stmtblock, &gs)); ECHK(csbe_ebb_implicit_return(csbe)); ECHK(csbe_funcbody_end(csbe)); return 1; err_csbe: return interr_csbe(ctx, e, INTERR_FUNCIR); } static int generate_funcbodies(struct CSlul *ctx, struct Csbe *csbe) { struct TopLevelIdent *tlident; for (tlident = ctx->impl.idents_list; tlident; tlident = tlident->next) { if (!IS_FUNC_DECL(&tlident->decl)) continue; if (tlident->decl.type.type == T_IMPORTED) continue; ZCHK(generate_func_ir(ctx, csbe, tlident)); } return 1; } static int add_libimports(struct CSlul *ctx, struct Csbe *csbe) { struct CSlulDependency *dep; for (dep = ctx->module.first_dep; dep; dep = dep->next) { /* TODO consider adding a "slul_" prefix to all module names */ struct CsbeLibrary *lib = csbe_add_libimport(csbe, node_nameptr(&dep->node), dep->node.length); ZCHK(lib); dep->importlib = lib; } return 1; } int generate_ir(struct CSlul *ctx, struct Csbe *csbe) { enum CsbeErr e; ctx->next_ir_literal = 0; ZCHK(add_libimports(ctx, csbe)); ZCHK(generate_all_typedefs(ctx, csbe)); ZCHK(generate_all_datadefs(ctx, csbe)); ZCHK(generate_all_funcdecls(ctx, csbe)); ECHK(csbe_defs_done(csbe)); ZCHK(generate_funcbodies(ctx, csbe)); return 1; err_csbe: return interr_csbe(ctx, e, INTERR_DEFSDONE); } #endif