Lower instance methods

e3e79cd294dd8e81174c7ea4a1f67a2da0b1d43e231a3092b8c3b6b6ef9b4f01
Creates v-tables in the data section.
Alexis Sellier committed ago 1 parent 37fa6115
lib/std/lang/lower.rad +231 -43
223 223
// Slice Layout
224 224
//
225 225
// A slice is a fat pointer consisting of a data pointer and length.
226 226
// `{ ptr: u32, len: u32 }`.
227 227
228 -
/// Offset of slice pointer in slice data structure.
228 +
/// Slice data pointer offset.
229 229
const SLICE_PTR_OFFSET: i32 = 0;
230 230
/// Offset of slice length in slice data structure.
231 231
const SLICE_LEN_OFFSET: i32 = 8;
232 232
233 +
// Trait Object Layout
234 +
//
235 +
// A trait object is a fat pointer consisting of a data pointer and a
236 +
// v-table pointer. `{ data: *T, vtable: *VTable }`.
237 +
238 +
/// Trait object data pointer offset.
239 +
const TRAIT_OBJ_DATA_OFFSET: i32 = 0;
240 +
/// Trait object v-table pointer offset.
241 +
const TRAIT_OBJ_VTABLE_OFFSET: i32 = 8;
242 +
233 243
// Tagged Value Layout (optionals, tagged unions)
234 244
//
235 245
// Optionals and unions use 1-byte tags. Results use 8-byte tags.
236 246
//
237 247
// `{ tag: u8, [padding], payload: T }`
846 856
                try lowerDataDecl(low, node, decl.value, true);
847 857
            }
848 858
            case ast::NodeValue::StaticDecl(decl) => {
849 859
                try lowerDataDecl(low, node, decl.value, false);
850 860
            }
861 +
            case ast::NodeValue::InstanceDecl { traitName, targetType, methods } => {
862 +
                try lowerInstanceDecl(low, node, traitName, targetType, &methods);
863 +
            }
851 864
            else => {},
852 865
        }
853 866
    }
854 867
    return defaultFnIdx;
855 868
}
927 940
    self.moduleGraph = graph;
928 941
    self.pkgName = pkgName;
929 942
    self.currentMod = nil;
930 943
}
931 944
932 -
/// Lower a function declaration to an IL function.
933 -
///
934 -
/// This sets up the per-function lowering state, processes parameters,
935 -
/// then lowers the function body into a CFG of basic blocks.
936 -
///
937 -
/// For throwing functions, the return type is a result aggregate (tagged union)
938 -
/// rather than the declared return type.
939 -
fn lowerFnDecl(self: *mut Lowerer, node: *ast::Node, decl: ast::FnDecl) -> ?*il::Fn throws (LowerError) {
940 -
    if not shouldLowerFn(&decl, self.options.buildTest) {
941 -
        return nil;
942 -
    }
943 -
    let case ast::NodeValue::Ident(name) = decl.name.value else {
944 -
        throw LowerError::ExpectedIdentifier;
945 -
    };
946 -
    let data = resolver::nodeData(self.resolver, node);
947 -
    let case resolver::Type::Fn(fnType) = data.ty else {
948 -
        throw LowerError::ExpectedFunction;
949 -
    };
950 -
    let isExtern = checkAttr(decl.attrs, ast::Attribute::Extern);
951 -
952 -
    // Build qualified function name for multi-module compilation.
953 -
    let qualName = qualifyName(self, nil, name);
954 -
955 -
    // Register function symbol for cross-package call resolution.
956 -
    if let sym = data.sym {
957 -
        registerFnSym(self, sym, qualName);
958 -
    }
945 +
/// Create a new function lowerer for a given function type and name.
946 +
fn fnLowerer(
947 +
    self: *mut Lowerer,
948 +
    node: *ast::Node,
949 +
    fnType: *resolver::FnType,
950 +
    qualName: *[u8]
951 +
) -> FnLowerer {
959 952
    let vars = try! alloc::allocSlice(self.arena, @sizeOf(VarData), @alignOf(VarData), fnType.localCount) as *mut [VarData];
960 953
    let params = try! alloc::allocSlice(self.arena, @sizeOf(FnParamBinding), @alignOf(FnParamBinding), fnType.paramTypesLen) as *mut [FnParamBinding];
961 954
    let blockData = try! alloc::allocSlice(self.arena, @sizeOf(BlockData), @alignOf(BlockData), INITIAL_BLOCKS) as *mut [BlockData];
962 955
    let loopStack = try! alloc::allocSlice(self.arena, @sizeOf(LoopCtx), @alignOf(LoopCtx), MAX_LOOP_DEPTH) as *mut [LoopCtx];
963 956
981 974
        returnReg: nil,
982 975
        srcLoc: undefined,
983 976
    };
984 977
    if self.options.debug {
985 978
        let modId = self.currentMod else {
986 -
            panic "lowerFnDecl: debug enabled but no current module";
979 +
            panic "fnLowerer: debug enabled but no current module";
987 980
        };
988 981
        fnLow.srcLoc = il::SrcLoc {
989 982
            moduleId: modId,
990 983
            offset: node.span.offset,
991 984
        };
992 985
    }
986 +
    return fnLow;
987 +
}
988 +
989 +
/// Lower a function declaration.
990 +
///
991 +
/// This sets up the per-function lowering state, processes parameters,
992 +
/// then lowers the function body into a CFG of basic blocks.
993 +
///
994 +
/// For throwing functions, the return type is a result aggregate
995 +
/// rather than the declared return type.
996 +
fn lowerFnDecl(self: *mut Lowerer, node: *ast::Node, decl: ast::FnDecl) -> ?*il::Fn throws (LowerError) {
997 +
    if not shouldLowerFn(&decl, self.options.buildTest) {
998 +
        return nil;
999 +
    }
1000 +
    let case ast::NodeValue::Ident(name) = decl.name.value else {
1001 +
        throw LowerError::ExpectedIdentifier;
1002 +
    };
1003 +
    let data = resolver::nodeData(self.resolver, node);
1004 +
    let case resolver::Type::Fn(fnType) = data.ty else {
1005 +
        throw LowerError::ExpectedFunction;
1006 +
    };
1007 +
    let isExtern = checkAttr(decl.attrs, ast::Attribute::Extern);
1008 +
1009 +
    // Build qualified function name for multi-module compilation.
1010 +
    let qualName = qualifyName(self, nil, name);
1011 +
1012 +
    // Register function symbol for cross-package call resolution.
1013 +
    if let sym = data.sym {
1014 +
        registerFnSym(self, sym, qualName);
1015 +
    }
1016 +
    let mut fnLow = fnLowerer(self, node, fnType, qualName);
1017 +
993 1018
    // If the function returns an aggregate or is throwing, prepend a hidden
994 1019
    // return parameter. The caller allocates the buffer and passes it
995 1020
    // as the first argument; the callee writes the return value into it.
996 -
    let needsRetReg = fnType.throwListLen > 0
997 -
        or (isAggregateType(*fnType.returnType) and not isSmallAggregate(*fnType.returnType));
998 -
    if needsRetReg and not isExtern {
1021 +
    if requiresReturnParam(fnType) and not isExtern {
999 1022
        fnLow.returnReg = nextReg(&mut fnLow);
1000 1023
    }
1001 -
    let lowParams = try lowerParams(&mut fnLow, *fnType, decl.sig.params);
1024 +
    let lowParams = try lowerParams(&mut fnLow, *fnType, decl.sig.params, nil);
1002 1025
    let func = try! alloc::alloc(self.arena, @sizeOf(il::Fn), @alignOf(il::Fn)) as *mut il::Fn;
1003 1026
1004 1027
    *func = il::Fn {
1005 1028
        name: qualName,
1006 1029
        params: lowParams,
1025 1048
    func.blocks = try lowerFnBody(&mut fnLow, body);
1026 1049
1027 1050
    return func;
1028 1051
}
1029 1052
1053 +
/// Build a qualified name of the form "Type::method".
1054 +
fn instanceMethodName(self: *mut Lowerer, modId: ?u16, typeName: *[u8], methodName: *[u8]) -> *[u8] {
1055 +
    let sepLen: u32 = 2; // "::"
1056 +
    let totalLen = typeName.len + sepLen + methodName.len;
1057 +
    let buf = try! alloc::allocSlice(self.arena, 1, 1, totalLen) as *mut [u8];
1058 +
    let mut pos: u32 = 0;
1059 +
1060 +
    pos += try! mem::copy(&mut buf[pos..], typeName);
1061 +
    pos += try! mem::copy(&mut buf[pos..], "::");
1062 +
    pos += try! mem::copy(&mut buf[pos..], methodName);
1063 +
1064 +
    // TODO: Assert that `pos` equals `totalLen`.
1065 +
1066 +
    return qualifyName(self, modId, &buf[..totalLen]);
1067 +
}
1068 +
1069 +
/// Build a v-table data name of the form "vtable::Type::Trait".
1070 +
fn vtableName(self: *mut Lowerer, modId: ?u16, typeName: *[u8], traitName: *[u8]) -> *[u8] {
1071 +
    let prefix = "vtable::";
1072 +
    let sepLen: u32 = 2; // "::"
1073 +
    let totalLen = prefix.len + typeName.len + sepLen + traitName.len;
1074 +
    let buf = try! alloc::allocSlice(self.arena, 1, 1, totalLen) as *mut [u8];
1075 +
    let mut pos: u32 = 0;
1076 +
1077 +
    pos += try! mem::copy(&mut buf[pos..], prefix);
1078 +
    pos += try! mem::copy(&mut buf[pos..], typeName);
1079 +
    pos += try! mem::copy(&mut buf[pos..], "::");
1080 +
    pos += try! mem::copy(&mut buf[pos..], traitName);
1081 +
1082 +
    // TODO: Assert that `pos` equals `totalLen`.
1083 +
1084 +
    return qualifyName(self, modId, &buf[..totalLen]);
1085 +
}
1086 +
1087 +
/// Lower an instance declaration (`instance Trait for Type { ... }`).
1088 +
///
1089 +
/// Each method in the instance block is lowered as a standalone function
1090 +
/// with a qualified name of the form `Type::method`. A read-only v-table
1091 +
/// data record is emitted containing pointers to these functions, ordered
1092 +
/// by the trait's method indices. The v-table is later referenced when
1093 +
/// constructing trait objects for dynamic dispatch.
1094 +
fn lowerInstanceDecl(
1095 +
    self: *mut Lowerer,
1096 +
    node: *ast::Node,
1097 +
    traitNameNode: *ast::Node,
1098 +
    targetTypeNode: *ast::Node,
1099 +
    methods: *ast::NodeList
1100 +
) throws (LowerError) {
1101 +
    // Look up the trait and type from the resolver.
1102 +
    let traitSym = resolver::nodeData(self.resolver, traitNameNode).sym
1103 +
        else throw LowerError::MissingSymbol(traitNameNode);
1104 +
    let case resolver::SymbolData::Trait(traitInfo) = traitSym.data
1105 +
        else throw LowerError::MissingMetadata;
1106 +
    let typeSym = resolver::nodeData(self.resolver, targetTypeNode).sym
1107 +
        else throw LowerError::MissingSymbol(targetTypeNode);
1108 +
1109 +
    let tName = traitSym.name;
1110 +
    let typeName = typeSym.name;
1111 +
1112 +
    // Lower each instance method as a regular function.
1113 +
    // Collect qualified names for the v-table.
1114 +
    let mut methodNames: [*[u8]; ast::MAX_TRAIT_METHODS] = undefined;
1115 +
1116 +
    for i in 0..methods.len {
1117 +
        let methodNode = methods.list[i];
1118 +
        let case ast::NodeValue::InstanceMethodDecl {
1119 +
            name, receiverName, receiverType, sig, body
1120 +
        } = methodNode.value else continue;
1121 +
1122 +
        let case ast::NodeValue::Ident(mName) = name.value else {
1123 +
            throw LowerError::ExpectedIdentifier;
1124 +
        };
1125 +
        let qualName = instanceMethodName(self, nil, typeName, mName);
1126 +
1127 +
        // Get the function type from the resolver.
1128 +
        let data = resolver::nodeData(self.resolver, methodNode);
1129 +
        let case resolver::Type::Fn(fnType) = data.ty else {
1130 +
            throw LowerError::ExpectedFunction;
1131 +
        };
1132 +
1133 +
        // Register the method symbol.
1134 +
        if let sym = data.sym {
1135 +
            registerFnSym(self, sym, qualName);
1136 +
        }
1137 +
1138 +
        // Lower the method as a normal function.
1139 +
        let mut fnLow = fnLowerer(self, methodNode, fnType, qualName);
1140 +
        if requiresReturnParam(fnType) {
1141 +
            fnLow.returnReg = nextReg(&mut fnLow);
1142 +
        }
1143 +
        let lowParams = try lowerParams(&mut fnLow, *fnType, sig.params, receiverName);
1144 +
        let func = try! alloc::alloc(self.arena, @sizeOf(il::Fn), @alignOf(il::Fn)) as *mut il::Fn;
1145 +
1146 +
        *func = il::Fn {
1147 +
            name: qualName,
1148 +
            params: lowParams,
1149 +
            returnType: ilType(self, *fnType.returnType),
1150 +
            isExtern: false,
1151 +
            blocks: &[],
1152 +
        };
1153 +
        if fnType.throwListLen > 0 {
1154 +
            func.returnType = il::Type::W64;
1155 +
        }
1156 +
        func.blocks = try lowerFnBody(&mut fnLow, body);
1157 +
        try pushFn(self, func);
1158 +
1159 +
        let method = resolver::findTraitMethod(traitInfo, mName)
1160 +
            else panic "lowerInstanceDecl: method not found in trait";
1161 +
1162 +
        methodNames[method.index] = qualName;
1163 +
    }
1164 +
1165 +
    // Create v-table in data section, used for dynamic dispatch.
1166 +
    let vName = vtableName(self, nil, typeName, tName);
1167 +
    let values = try! alloc::allocSlice(
1168 +
        self.arena, @sizeOf(il::DataValue), @alignOf(il::DataValue), traitInfo.methodsLen
1169 +
    ) as *mut [il::DataValue];
1170 +
1171 +
    for i in 0..traitInfo.methodsLen {
1172 +
        values[i] = il::DataValue {
1173 +
            item: il::DataItem::Fn(methodNames[i]),
1174 +
            count: 1,
1175 +
        };
1176 +
    }
1177 +
    try pushData(self, il::Data {
1178 +
        name: vName,
1179 +
        size: traitInfo.methodsLen * resolver::PTR_SIZE,
1180 +
        alignment: resolver::PTR_SIZE,
1181 +
        readOnly: true,
1182 +
        isUndefined: false,
1183 +
        values: &values[..traitInfo.methodsLen],
1184 +
    });
1185 +
}
1186 +
1030 1187
/// Check if a function should be lowered.
1031 1188
fn shouldLowerFn(decl: *ast::FnDecl, buildTest: bool) -> bool {
1032 1189
    if checkAttr(decl.attrs, ast::Attribute::Test) {
1033 1190
        return buildTest;
1034 1191
    }
2857 3014
        newArgs[i] = il::Val::Undef;
2858 3015
    }
2859 3016
    return newArgs;
2860 3017
}
2861 3018
3019 +
/// Extract the parameter name from an [`FnParam`] AST node value.
3020 +
fn paramName(value: *ast::NodeValue) -> *[u8] throws (LowerError) {
3021 +
    let case ast::NodeValue::FnParam(param) = *value else {
3022 +
        throw LowerError::ExpectedFunctionParam;
3023 +
    };
3024 +
    let case ast::NodeValue::Ident(name) = param.name.value else {
3025 +
        throw LowerError::ExpectedIdentifier;
3026 +
    };
3027 +
    return name;
3028 +
}
3029 +
2862 3030
/// Lower function parameters. Declares variables for each parameter.
3031 +
/// When a receiver name is passed, we're handling a trait method.
2863 3032
fn lowerParams(
2864 3033
    self: *mut FnLowerer,
2865 3034
    fnType: resolver::FnType,
2866 -
    astParams: ast::NodeList
3035 +
    astParams: ast::NodeList,
3036 +
    receiverName: ?*ast::Node
2867 3037
) -> *[il::Param] throws (LowerError) {
2868 3038
    let offset: u32 = 1 if self.returnReg != nil else 0;
2869 3039
    let totalLen = fnType.paramTypesLen + offset;
2870 -
2871 3040
    if totalLen == 0 {
2872 3041
        return &[];
2873 3042
    }
2874 3043
    debug::check(fnType.paramTypesLen <= resolver::MAX_FN_PARAMS);
2875 3044
2884 3053
        let type = ilType(self.low, *fnType.paramTypes[i]);
2885 3054
        let reg = nextReg(self);
2886 3055
2887 3056
        params[i + offset] = il::Param { value: reg, type };
2888 3057
2889 -
        // Declare the parameter variable.
2890 -
        let paramNode = astParams.list[i];
2891 -
        let case ast::NodeValue::FnParam(param) = paramNode.value else {
2892 -
            throw LowerError::ExpectedFunctionParam;
2893 -
        };
2894 -
        let case ast::NodeValue::Ident(name) = param.name.value else {
2895 -
            throw LowerError::ExpectedIdentifier;
2896 -
        };
3058 +
        // Declare the parameter variable. For the receiver, the name comes
3059 +
        // from the receiver node.
3060 +
        // For all other parameters, the name comes from the AST params.
3061 +
        let mut name: *[u8] = undefined;
3062 +
        if let recNode = receiverName {
3063 +
            if i == 0 {
3064 +
                let case ast::NodeValue::Ident(recName) = recNode.value else {
3065 +
                    throw LowerError::ExpectedIdentifier;
3066 +
                };
3067 +
                name = recName;
3068 +
            } else {
3069 +
                name = try paramName(&astParams.list[i - 1].value);
3070 +
            }
3071 +
        } else {
3072 +
            name = try paramName(&astParams.list[i].value);
3073 +
        }
2897 3074
        let v = newVar(self, name, type, false, il::Val::Undef);
2898 3075
2899 3076
        self.params[self.paramsLen] = FnParamBinding { var: v, reg };
2900 3077
        self.paramsLen += 1;
2901 3078
    }
3713 3890
        }
3714 3891
        else => return false,
3715 3892
    }
3716 3893
}
3717 3894
3895 +
/// Whether a function needs a hidden return parameter.
3896 +
///
3897 +
/// This is the case for throwing functions, which return a result aggregate,
3898 +
/// and for functions returning large aggregates that cannot be passed in
3899 +
/// registers.
3900 +
fn requiresReturnParam(fnType: *resolver::FnType) -> bool {
3901 +
    return fnType.throwListLen > 0
3902 +
        or (isAggregateType(*fnType.returnType)
3903 +
        and not isSmallAggregate(*fnType.returnType));
3904 +
}
3905 +
3718 3906
/// Check if a node is a void union variant literal (e.g. `Color::Red`).
3719 3907
/// If so, returns the variant's tag index. This enables optimized comparisons
3720 3908
/// that only check the tag instead of doing full aggregate comparison.
3721 3909
fn voidVariantIndex(res: *resolver::Resolver, node: *ast::Node) -> ?i64 {
3722 3910
    let data = resolver::nodeData(res, node);
lib/std/lang/resolver.rad +1 -1
3277 3277
3278 3278
    return sym;
3279 3279
}
3280 3280
3281 3281
/// Find a trait method by name.
3282 -
fn findTraitMethod(traitType: *TraitType, name: *[u8]) -> ?*TraitMethod {
3282 +
pub fn findTraitMethod(traitType: *TraitType, name: *[u8]) -> ?*TraitMethod {
3283 3283
    for i in 0..traitType.methodsLen {
3284 3284
        if traitType.methods[i].name == name {
3285 3285
            return &traitType.methods[i];
3286 3286
        }
3287 3287
    }