File:  [parser3project] / parser3 / src / types / pa_vmethod_frame.h
Revision 1.149: download - view: text, annotated - select for diffs - revision graph
Sat Sep 26 21:34:36 2026 UTC (4 weeks ago) by moko
Branches: MAIN
CVS tags: HEAD
SINGLE_METHOD_FRAME implemented to reduce execute() stack frame. Without it, compiler
reserves stack frame for each ot 8 parser frame variants (related to feature #1293)

/** @file
	Parser: @b method_frame write context

	Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com)
	Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru>
*/

#ifndef PA_VMETHOD_FRAME_H
#define PA_VMETHOD_FRAME_H

#define IDENT_PA_VMETHOD_FRAME_H "$Id: pa_vmethod_frame.h,v 1.149 2026/09/26 21:34:36 moko Exp $"

#include "pa_symbols.h"
#include "pa_wcontext.h"
#include "pa_vvoid.h"
#include "pa_vjunction.h"
#include "pa_inline_hash.h"

// forwards

class Request;

/**
	@b method parameters passed in this array.
	contains handy typecast ad junction/not junction ensurers
*/

class MethodParams {
public:
	MethodParams() : felements(0), fsize(0) {}

#ifdef USE_DESTRUCTORS
	~MethodParams() {
		Value **flast=felements+count();
		for(Value **current=felements;current<flast;current++){
			Junction* junction=(*current)->get_junction();
			if (junction && junction->code)
				delete (VJunction*)(*current);
		}
	}
#endif

	void store_params(Value **params, size_t count) {
		felements=params;
		fsize=count;
	}

	inline size_t count() const { return fsize; }

	inline Value& get(size_t index) const {
		assert(index<count());
		return *felements[index];
	}

	inline Value& operator[] (size_t index) { return get(index); }

	/// handy is-value-a-junction ensurer
	Value& as_junction(int index, const char* msg) {
		Value& value=get(index);
		if(value.get_junction())
			return value;
		throw Exception(PARSER_RUNTIME, 0, "%s (parameter #%d is '%s')", msg, 1+index, value.type());
	}

	/// handy value-is-not-a-junction ensurer
	Value& as_no_junction(int index, const char* msg) {
		Value& value=get(index);
		if(!value.get_junction())
			return value;
		throw Exception(PARSER_RUNTIME, 0, "%s (parameter #%d is '%s')", msg, 1+index, value.type());
	}

	/// handy is-value-a-junction ensurer or can be auto-processed
	Value& as_expression(int index, const char* msg) {
		Value& value=get(index);
		if(value.is_evaluated_expr())
			return value;
		if(value.get_junction())
			return value;
		throw Exception(PARSER_RUNTIME, 0, "%s (parameter #%d is '%s')", msg, 1+index, value.type());
	}

	/// handy expression auto-processing to double
	double as_double(int index, const char* msg, Request& r) {
		Value& value=get(index);
		if(value.is_evaluated_expr())
			return value.as_double();
		return get_processed(value, msg, index, r).as_double();
	}

	/// handy expression auto-processing to int
	int as_int(int index, const char* msg, Request& r) {
		Value& value=get(index);
		if(value.is_evaluated_expr())
			return value.as_int();
		return get_processed(value, msg, index, r).as_int();
	}

	/// handy expression auto-processing to int
	pa_wint as_wint(int index, const char* msg, Request& r) {
		Value& value=get(index);
		if(value.is_evaluated_expr())
			return value.as_wint();
		return get_processed(value, msg, index, r).as_wint();
	}

	/// handy param auto-processing to index
	int as_index(int index, size_t count, Request& r) {
		if(get(index).is_string()) {
			const String& svalue=*get(index).get_string();
			if(svalue == "last")
				return count-1;
			else if(svalue != "first")
				throw Exception(PARSER_RUNTIME, &svalue, "index must be 'first', 'last' or expression");
			return 0;
		} else {
			int result=as_int(index, "index must be 'first', 'last' or expression", r);
			if(result < 0)
				result+=count;
			return result;
		}
	}

	/// handy expression auto-processing to bool
	bool as_bool(int index, const char* msg, Request& r) {
		Value& value=get(index);
		if(value.is_evaluated_expr())
			return value.as_bool();
		return get_processed(value, msg, index, r).as_bool();
	}

	/// handy string ensurer
	const String& as_string(int index, const char* msg) {
		if(const String *result=get(index).get_string())
			return *result;
		throw Exception(PARSER_RUNTIME, 0, "%s (parameter #%d is '%s')", msg, 1+index, get(index).type());
	}

	/// handy file name ensurer
	const String& as_file_name(int index);

	/// handy file name from string or file
	const String& as_file_spec(int index);

	/// handy hash ensurers
	HashStringValue* as_hash(int index, const char* name=0);

	/// handy table ensurer
	Table* as_table(int index, const char* name=0);

private:

	Value **felements;
	size_t fsize;

	Value& get_processed(Value& value, const char* msg, int index, Request& r);

};

/**
	Method frame write context
	accepts values written by method code
*/

class VMethodFrame: public WContext {
protected:
	VMethodFrame *fcaller;
	Value& fself;

public: // Value

	override const char* type() const { return "method_frame"; }

	/// VMethodFrame: self_transparent
	override VStateless_class* get_class() { return self().get_class(); }

	/// VMethodFrame: self_transparent
	override VStateless_class* base() { return self().base(); }

public: // usage

	VMethodFrame(const Method& amethod, VMethodFrame *acaller, Value& aself):
		WContext(0 /* no parent, junctions can be reattached only up to VMethodFrame */),
		fcaller(acaller),
		fself(aself),
		method(amethod) {
	}

	VMethodFrame *caller() { return fcaller; }

	Value& self() { return fself; }

	void check_call_type(){
		if(method.call_type==Method::CT_ANY)
			return;

		Method::Call_type call_type=self().get_class()==&self() ? Method::CT_STATIC : Method::CT_DYNAMIC;

		 if(method.call_type!=call_type) // call type not allowed?
			throw Exception(PARSER_RUNTIME, method.name, "method of '%s' is not allowed to be called %s", 
				self().type(), call_type==Method::CT_STATIC ? "statically" : "dynamically");
	}

public:

	const Method& method;
};


class VNativeMethodFrame: public VMethodFrame {
protected:
	MethodParams fnumbered_params;

public: // usage

	VNativeMethodFrame(const Method& amethod, VMethodFrame *acaller, Value& aself) : VMethodFrame(amethod, acaller, aself){}

	// params should be declared outside of *_FRAME_ACTION as MethodParams destructor will be called in ~VNativeMethodFrame
	void store_params(Value **params, size_t count) {
		fnumbered_params.store_params(params, count);
		method.check_actual_numbered_params(self(), &fnumbered_params);
	}

	void empty_params() {
		method.check_actual_numbered_params(self(), &fnumbered_params);
	}

	void call(Request &r);

};


/**
	Handles named parameters and local variables
*/

class VParserMethodFrame: public VMethodFrame {
public: // Value

	/// VParserMethodFrame: $result | parent get_string(=accumulated fstring)
	override const String* get_string() {
		// if we have $result, return it's string value, else return as usual: accumulated fstring or fvalue
		return my_result ? my_result->get_string() : WContext::get_string();
	}

	/// VParserMethodFrame: my or self_transparent or $caller
	override Value* get_element(const String& aname) {
		if(SYMBOLS_EQ(aname,RESULT_SYMBOL))
			return my_result ? my_result : VVoid::get();

		if(SYMBOLS_EQ(aname,CALLER_SYMBOL))
			return get_caller_wrapper();

		if(SYMBOLS_EQ(aname,SELF_SYMBOL))
			return &self();

		if(Value* result=my.get(aname))
			return result;

		if(Value* result=self().get_element(aname))
			return result;

		return 0;
	}

	/// VParserMethodFrame: my or self_transparent
	override const VJunction* put_element(const String& aname, Value* avalue) {
		if(SYMBOLS_EQ(aname,RESULT_SYMBOL)){
			my_result=avalue;
#ifdef OPTIMIZE_RESULT
			// This check is only for maintaining consistency. Even if we change the mode from RO_USE_WCONTEXT, it will not work because
			// CO_WITHOUT_WCONTEXT is also active. In this mode writes go directly to the parent context and result() is never called.
			// If we also change CO_WITHOUT_WCONTEXT, the current method call will still continue writing to the parent context and
			// result() will not be called. Only subsequent method calls will use $result.
			// The current behavior is more predictable: the first method call determines result_optimization, and subsequent calls never change it.
			if(method.result_optimization==Method::RO_UNKNOWN)
				((Method *)&method)->result_optimization=Method::RO_USE_RESULT;
#endif
			return 0;
		}
		if(my.put_replaced(aname, avalue))
			return 0;
		return self().put_element(aname, avalue);
	}

public: // WContext

	/// VParserMethodFrame: skip write when RO_USE_RESULT
	override void write(const String& astring) {
#ifdef OPTIMIZE_RESULT
		if(method.result_optimization != Method::RO_USE_RESULT)
#endif
			WContext::write(astring);
	}

	override void write(Value& avalue) {
		WContext::write(avalue);
	}

	override ValueRef result() {
		// if we have $result, return it, else return as usual: accumulated fstring or fvalue
		if(my_result)
			return my_result;
#ifdef OPTIMIZE_RESULT
		if(method.result_optimization==Method::RO_USE_RESULT)
			return VVoid::get();

		// Due to call optimization, the following code is called only once.
		// CO_WITHOUT_WCONTEXT means subsequent writes will be to the parent context, and the result() won't be called.

		((Method *)&method)->result_optimization=Method::RO_USE_WCONTEXT;
#ifdef OPTIMIZE_CALL // nested as CO_WITHOUT_WCONTEXT assumes that $result not used
		((Method *)&method)->call_optimization=Method::CO_WITHOUT_WCONTEXT;
#endif
#endif
		return WContext::result();
	}

public: // usage

#ifdef OPTIMIZE_MY_HASH
	typedef InlineHashString<Value*> MyHash;
#else
	typedef HashString<Value*> MyHash;
#endif
	MyHash my; // public for ^stack[]
	Value* my_result;

	VParserMethodFrame(const Method& amethod, VMethodFrame *acaller, Value& aself);

	void store_params(Value **params, size_t count) {
		size_t param_count=method.params_count;
		size_t i=0;

		if(count>param_count){

			for(; i<param_count; i++) {
				const String& fname=*(*method.params_names)[i];
				set_my_variable(fname, params[i]);
			}

			if(method.extra_params){
				VHash& vargs=*new VHash();
				HashStringValue& args = vargs.hash();

				for(; i<count; i++) {
					args.put(String::Body::uitoa(args.count()), params[i]);
				}

				set_my_variable(*method.extra_params, &vargs);
			} else if(method.named_params){
				if(count!=param_count+1)
					throw Exception(PARSER_RUNTIME, method.name, "method of '%s' accepts maximum %d parameter(s) (%d present)", self().type(), param_count+1, count);

				HashStringValue* named_args = params[i]->as_hash("named parameter");
				size_t named_count=method.named_params->count();
 				for(i=0; i<named_count; i++) {
					const String& fname=*(*method.named_params)[i];
					Value *arg=named_args ? named_args->get(fname) : NULL;
					set_my_variable(fname, arg ? arg : VVoid::get());
				}
			} else
				throw Exception(PARSER_RUNTIME, method.name, "method of '%s' accepts maximum %d parameter(s) (%d present)", self().type(), param_count, count);
		} else {

			for(; i<count; i++) {
				const String& fname=*(*method.params_names)[i];
				set_my_variable(fname, params[i]);
			}

			for(; i<param_count; i++) {
				const String& fname=*(*method.params_names)[i];
				set_my_variable(fname, VVoid::get());
			}

			if(method.extra_params){
				set_my_variable(*method.extra_params, VVoid::get());
			} else if(method.named_params){
				size_t named_count=method.named_params->count();
				for(i=0; i<named_count; i++) {
					const String& fname=*(*method.named_params)[i];
					set_my_variable(fname, VVoid::get());
				}
			}
		}
	}

	void empty_params(){
		size_t param_count=method.params_count;
		if(param_count>0){
			set_my_variable(*(*method.params_names)[0], VString::empty());
			for(size_t i=1; i<param_count; i++)
				set_my_variable(*(*method.params_names)[i], VVoid::get());
		}
		if(method.extra_params){
			set_my_variable(*method.extra_params, VVoid::get());
		} else if(method.named_params){
			size_t named_count=method.named_params->count();
			for(size_t i=0; i<named_count; i++) {
				const String& fname=*(*method.named_params)[i];
				set_my_variable(fname, VVoid::get());
			}
		}
	}

	void call(Request &r);

protected:

	inline void set_my_variable(const String& aname, Value* value) {
		if(SYMBOLS_EQ(aname,RESULT_SYMBOL)){
			my_result=value;
#ifdef OPTIMIZE_RESULT
			((Method *)&method)->result_optimization=Method::RO_USE_RESULT;
#endif
			return;
		}
		my.put(aname, value); // remember param
	}

	Value* get_caller_wrapper();

};


class VLocalParserMethodFrame: public VParserMethodFrame {
public: // Value

	override const VJunction* put_element(const String& aname, Value* avalue){
		set_my_variable(aname, avalue);
		return 0;
	}

public: // usage

	VLocalParserMethodFrame(const Method& amethod, VMethodFrame *acaller, Value& aself) : VParserMethodFrame(amethod, acaller, aself) {}

};


template<typename Parent> class VExpressionFrame: public Parent {
public:
	VExpressionFrame(const Method& amethod, VMethodFrame *acaller, Value& aself) : Parent(amethod, acaller, aself) {}

	/// in expressions only strings are written as strings
	override void write_as_string(Value& avalue) {
		if(avalue.is_string())
			Parent::write(*avalue.get_string());
		else
			Parent::write(avalue);
	}
};


template<typename Parent> class VConstructorFrame: public Parent {
public:
	VConstructorFrame(const Method& amethod, VMethodFrame *acaller, Value& aself) : Parent(amethod, acaller, aself) {
		// prevent non-string writes for better error reporting [constructors are not expected to return anything]
		VMethodFrame::write(aself);
	}

	override void write(const String& /*astring*/) {}
};


#define METHOD_FRAME_ACTION(method, caller, self, action)					\
	if((method).native_code){								\
		VNativeMethodFrame frame(method, caller, self);					\
		action;										\
	} else {										\
		if((method).all_vars_local){							\
			VLocalParserMethodFrame frame(method, caller, self);			\
			action;									\
		} else {									\
			VParserMethodFrame frame(method, caller, self);				\
			action;									\
		}										\
	}

#define EXPRESSION_FRAME_ACTION(method, caller, self, action)					\
	if((method).native_code){								\
		VExpressionFrame<VNativeMethodFrame> frame(method, caller, self);		\
		action;										\
	} else {										\
		if((method).all_vars_local){							\
			VLocalParserMethodFrame frame(method, caller, self);			\
			action;									\
		} else {									\
			VParserMethodFrame frame(method, caller, self);				\
			action;									\
		}										\
	}

#define CONSTRUCTOR_FRAME_ACTION(method, caller, self, action)					\
	if((method).native_code){								\
		VConstructorFrame<VNativeMethodFrame> frame(method, caller, self);		\
		action;										\
	} else {										\
		if((method).all_vars_local){							\
			VConstructorFrame<VLocalParserMethodFrame> frame(method, caller, self);	\
			action;									\
		} else {									\
			VConstructorFrame<VParserMethodFrame> frame(method, caller, self);	\
			action;									\
		}										\
	}


// execute() has 8 parser frame variants and the compiler reserves stack room for all of them.
// METHOD_FRAME is now ~300 bytes due to inlined args/locals hash.
// SINGLE_METHOD_FRAME makes execute() construct them all in one shared storage.

//#define WITHOUT_SINGLE_METHOD_FRAME

#ifdef WITHOUT_SINGLE_METHOD_FRAME

#define SINGLE_METHOD_FRAME_STORAGE ((void)0)
#define SINGLE_METHOD_FRAME_ACTION METHOD_FRAME_ACTION
#define SINGLE_EXPRESSION_FRAME_ACTION EXPRESSION_FRAME_ACTION
#define SINGLE_CONSTRUCTOR_FRAME_ACTION CONSTRUCTOR_FRAME_ACTION

#else

/// storage for one method frame of any kind: parser frame is the largest of them
union Method_frame_storage {
	char frame[sizeof(VParserMethodFrame)];
	long double align_number;
	void* align_pointer;
};

/// Auto-object used for constructing a method frame in the storage shared by the whole function
template<typename Frame> class Temp_method_frame {
	Frame& fframe;
	Temp_method_frame(const Temp_method_frame&);
	Temp_method_frame& operator=(const Temp_method_frame&);
public:
	Temp_method_frame(Method_frame_storage& storage, const Method& method, VMethodFrame* caller, Value& self):
		fframe(*::new(static_cast<void*>(&storage)) Frame(method, caller, self)) {
		// catch a frame outgrowing the storage at compilation
		typedef char Frame_fits_storage[sizeof(Frame)<=sizeof(Method_frame_storage) ? 1 : -1];
		(void)sizeof(Frame_fits_storage);
	}
	~Temp_method_frame() { fframe.~Frame(); }
	Frame& get() { return fframe; }
};

#define SINGLE_METHOD_FRAME_STORAGE Method_frame_storage frame_storage

#define TEMP_METHOD_FRAME_ACTION(Frame, method, caller, self, action) {				\
		Temp_method_frame<Frame> temp_frame(frame_storage, method, caller, self);	\
		Frame& frame=temp_frame.get();							\
		action;										\
	}

#define SINGLE_METHOD_FRAME_ACTION(method, caller, self, action)				\
	if((method).native_code){								\
		TEMP_METHOD_FRAME_ACTION(VNativeMethodFrame, method, caller, self, action)	\
	} else if((method).all_vars_local){							\
		TEMP_METHOD_FRAME_ACTION(VLocalParserMethodFrame, method, caller, self, action)	\
	} else {										\
		TEMP_METHOD_FRAME_ACTION(VParserMethodFrame, method, caller, self, action)	\
	}

#define SINGLE_EXPRESSION_FRAME_ACTION(method, caller, self, action)				\
	if((method).native_code){								\
		TEMP_METHOD_FRAME_ACTION(VExpressionFrame<VNativeMethodFrame>, method, caller, self, action) \
	} else if((method).all_vars_local){							\
		TEMP_METHOD_FRAME_ACTION(VLocalParserMethodFrame, method, caller, self, action)	\
	} else {										\
		TEMP_METHOD_FRAME_ACTION(VParserMethodFrame, method, caller, self, action)	\
	}

#define SINGLE_CONSTRUCTOR_FRAME_ACTION(method, caller, self, action)				\
	if((method).native_code){								\
		TEMP_METHOD_FRAME_ACTION(VConstructorFrame<VNativeMethodFrame>, method, caller, self, action) \
	} else if((method).all_vars_local){							\
		TEMP_METHOD_FRAME_ACTION(VConstructorFrame<VLocalParserMethodFrame>, method, caller, self, action) \
	} else {										\
		TEMP_METHOD_FRAME_ACTION(VConstructorFrame<VParserMethodFrame>, method, caller, self, action) \
	}

#endif

#endif

E-mail: