[6/18] d3dx9: Shader assembler VS/PS 3.0 support
Matteo Bruni
matteo.mystral at gmail.com
Sun Aug 16 12:52:03 CDT 2009
-------------- next part --------------
From dd8c1742a8617b4586c0741f1f561e123898bffd Mon Sep 17 00:00:00 2001
From: Matteo Bruni <matteo.mystral at gmail.com>
Date: Sat, 15 Aug 2009 21:28:02 +0200
Subject: d3dx9: Shader assembler VS/PS 3.0 support
---
dlls/d3dx9_36/asmparser.c | 219 ++++++++++++++++++++++++++++++++++
dlls/d3dx9_36/asmshader.y | 4 +-
dlls/d3dx9_36/bytecodewriter.c | 257 +++++++++++++++++++++++++++++++++++++++-
3 files changed, 476 insertions(+), 4 deletions(-)
diff --git a/dlls/d3dx9_36/asmparser.c b/dlls/d3dx9_36/asmparser.c
index a880b62..6f34ffb 100644
--- a/dlls/d3dx9_36/asmparser.c
+++ b/dlls/d3dx9_36/asmparser.c
@@ -154,3 +154,222 @@ static void asmparser_instr(struct asm_parser *This, DWORD opcode,
}
}
+/* Checks if the source modifier is not supported in VS (all versions) or
+ PS 2.0 and newer */
+static void check_legacy_srcmod(struct asm_parser *This, DWORD srcmod) {
+ if(srcmod == BWRITERSPSM_BIAS || srcmod == BWRITERSPSM_BIASNEG ||
+ srcmod == BWRITERSPSM_SIGN || srcmod == BWRITERSPSM_SIGNNEG ||
+ srcmod == BWRITERSPSM_COMP || srcmod == BWRITERSPSM_X2 ||
+ srcmod == BWRITERSPSM_X2NEG || srcmod == BWRITERSPSM_DZ ||
+ srcmod == BWRITERSPSM_DW) {
+ asmparser_message(This, "Line %u: Source modifier %s not supported in this shader version\n",
+ This->line_no,
+ debug_print_srcmod(srcmod));
+ set_parse_status(This, PARSE_ERR);
+ }
+}
+
+static void check_loop_swizzle(struct asm_parser *This,
+ const struct shader_reg *src) {
+ if((src->type == BWRITERSPR_LOOP && src->swizzle != BWRITERVS_NOSWIZZLE) ||
+ (src->rel_reg && src->rel_reg->type == BWRITERSPR_LOOP &&
+ src->rel_reg->swizzle != BWRITERVS_NOSWIZZLE)) {
+ asmparser_message(This, "Line %u: Swizzle not allowed on aL register\n", This->line_no);
+ set_parse_status(This, PARSE_ERR);
+ }
+}
+
+struct allowed_reg_type {
+ DWORD type;
+ DWORD count;
+};
+
+static BOOL check_reg_type(const struct shader_reg *reg,
+ const struct allowed_reg_type *allowed) {
+ unsigned int i = 0;
+
+ while(allowed[i].type != ~0U) {
+ if(reg->type == allowed[i].type) {
+ if(reg->rel_reg) return TRUE; /* The relative addressing register
+ can have a negative value, we
+ can't check the register index */
+ if(reg->regnum < allowed[i].count) return TRUE;
+ else return FALSE;
+ }
+ i++;
+ }
+ return FALSE;
+}
+
+struct allowed_reg_type vs_3_reg_allowed[] = {
+ { BWRITERSPR_TEMP, 32 },
+ { BWRITERSPR_INPUT, 16 },
+ { BWRITERSPR_CONST, ~0U },
+ { BWRITERSPR_ADDR, 1 },
+ { BWRITERSPR_CONSTBOOL, 16 },
+ { BWRITERSPR_CONSTINT, 16 },
+ { BWRITERSPR_LOOP, 1 },
+ { BWRITERSPR_LABEL, 2048 },
+ { BWRITERSPR_PREDICATE, 1 },
+ { BWRITERSPR_SAMPLER, 4 },
+ { BWRITERSPR_OUTPUT, 12 },
+ { ~0U, 0 } /* End tag */
+};
+
+static void asmparser_srcreg_vs_3(struct asm_parser *This,
+ struct instruction *instr, int num,
+ const struct shader_reg *src) {
+ if(!check_reg_type(src, vs_3_reg_allowed)) {
+ asmparser_message(This, "Line %u: Source register %s not supported in VS 3.0\n",
+ This->line_no,
+ debug_print_srcreg(src, ST_VERTEX));
+ set_parse_status(This, PARSE_ERR);
+ }
+ check_loop_swizzle(This, src);
+ check_legacy_srcmod(This, src->srcmod);
+ memcpy(&instr->src[num], src, sizeof(*src));
+}
+
+struct allowed_reg_type ps_3_reg_allowed[] = {
+ { BWRITERSPR_INPUT, 10 },
+ { BWRITERSPR_TEMP, 32 },
+ { BWRITERSPR_CONST, 224 },
+ { BWRITERSPR_CONSTINT, 16 },
+ { BWRITERSPR_CONSTBOOL, 16 },
+ { BWRITERSPR_PREDICATE, 1 },
+ { BWRITERSPR_SAMPLER, 16 },
+ { BWRITERSPR_MISCTYPE, 2 }, /* vPos and vFace */
+ { BWRITERSPR_LOOP, 1 },
+ { BWRITERSPR_LABEL, 2048 },
+ { BWRITERSPR_COLOROUT, ~0U },
+ { BWRITERSPR_DEPTHOUT, 1 },
+ { ~0U, 0 } /* End tag */
+};
+
+static void asmparser_srcreg_ps_3(struct asm_parser *This,
+ struct instruction *instr, int num,
+ const struct shader_reg *src) {
+ if(!check_reg_type(src, ps_3_reg_allowed)) {
+ asmparser_message(This, "Line %u: Source register %s not supported in PS 3.0\n",
+ This->line_no,
+ debug_print_srcreg(src, ST_PIXEL));
+ set_parse_status(This, PARSE_ERR);
+ }
+ check_loop_swizzle(This, src);
+ check_legacy_srcmod(This, src->srcmod);
+ memcpy(&instr->src[num], src, sizeof(*src));
+}
+
+static void asmparser_dstreg_vs_3(struct asm_parser *This,
+ struct instruction *instr,
+ const struct shader_reg *dst) {
+ if(!check_reg_type(dst, vs_3_reg_allowed)) {
+ asmparser_message(This, "Line %u: Destination register %s not supported in VS 3.0\n",
+ This->line_no,
+ debug_print_dstreg(dst, ST_VERTEX));
+ set_parse_status(This, PARSE_ERR);
+ }
+ memcpy(&instr->dst, dst, sizeof(*dst));
+ instr->has_dst = TRUE;
+}
+
+static void asmparser_dstreg_ps_3(struct asm_parser *This,
+ struct instruction *instr,
+ const struct shader_reg *dst) {
+ if(!check_reg_type(dst, ps_3_reg_allowed)) {
+ asmparser_message(This, "Line %u: Destination register %s not supported in PS 3.0\n",
+ This->line_no,
+ debug_print_dstreg(dst, ST_PIXEL));
+ set_parse_status(This, PARSE_ERR);
+ }
+ memcpy(&instr->dst, dst, sizeof(*dst));
+ instr->has_dst = TRUE;
+}
+
+static void asmparser_predicate_supported(struct asm_parser *This,
+ const struct shader_reg *predicate) {
+ /* this sets the predicate of the last instruction added to the shader */
+ if(!This->shader) return;
+ if(This->shader->num_instrs == 0){
+ asmparser_message(This, "Line %u: Predicate on the first shader instruction\n", This->line_no);
+ set_parse_status(This, PARSE_ERR);
+ return;
+ }
+ This->shader->instr[This->shader->num_instrs-1]->has_predicate = TRUE;
+ memcpy(&This->shader->instr[This->shader->num_instrs-1]->predicate, predicate, sizeof(*predicate));
+}
+
+static void asmparser_coissue_unsupported(struct asm_parser *This) {
+ asmparser_message(This, "Line %u: Coissue is only supported in pixel shaders versions <= 1.4\n", This->line_no);
+ set_parse_status(This, PARSE_ERR);
+}
+
+static struct asmparser_backend parser_vs_3 = {
+ asmparser_constF,
+ asmparser_constI,
+ asmparser_constB,
+
+ asmparser_dstreg_vs_3,
+ asmparser_srcreg_vs_3,
+
+ asmparser_predicate_supported,
+ asmparser_coissue_unsupported,
+
+ asmparser_dcl_output,
+ asmparser_dcl_input,
+ asmparser_dcl_sampler,
+
+ asmparser_end,
+
+ asmparser_instr,
+};
+
+static struct asmparser_backend parser_ps_3 = {
+ asmparser_constF,
+ asmparser_constI,
+ asmparser_constB,
+
+ asmparser_dstreg_ps_3,
+ asmparser_srcreg_ps_3,
+
+ asmparser_predicate_supported,
+ asmparser_coissue_unsupported,
+
+ asmparser_dcl_output,
+ asmparser_dcl_input,
+ asmparser_dcl_sampler,
+
+ asmparser_end,
+
+ asmparser_instr,
+};
+
+void create_vs30_parser(struct asm_parser *ret) {
+ TRACE_(parsed_shader)("vs_3_0\n");
+
+ ret->shader = asm_alloc(sizeof(*ret->shader));
+ if(!ret->shader) {
+ ERR("Failed to allocate memory for the shader\n");
+ set_parse_status(ret, PARSE_ERR);
+ return;
+ }
+
+ ret->shader->type = ST_VERTEX;
+ ret->shader->version = BWRITERVS_VERSION(3, 0);
+ ret->funcs = &parser_vs_3;
+}
+
+void create_ps30_parser(struct asm_parser *ret) {
+ TRACE_(parsed_shader)("ps_3_0\n");
+
+ ret->shader = asm_alloc(sizeof(*ret->shader));
+ if(!ret->shader) {
+ ERR("Failed to allocate memory for the shader\n");
+ set_parse_status(ret, PARSE_ERR);
+ return;
+ }
+
+ ret->shader->type = ST_PIXEL;
+ ret->shader->version = BWRITERPS_VERSION(3, 0);
+ ret->funcs = &parser_ps_3;
+}
diff --git a/dlls/d3dx9_36/asmshader.y b/dlls/d3dx9_36/asmshader.y
index 6e2f78e..fe2e204 100644
--- a/dlls/d3dx9_36/asmshader.y
+++ b/dlls/d3dx9_36/asmshader.y
@@ -320,7 +320,7 @@ version_marker: VER_VS10
| VER_VS30
{
TRACE("Vertex shader 3.0\n");
- /* TODO: create the appropriate parser context */
+ create_vs30_parser(ctx);
}
| VER_PS10
{
@@ -360,7 +360,7 @@ version_marker: VER_VS10
| VER_PS30
{
TRACE("Pixel shader 3.0\n");
- /* TODO: create the appropriate parser context */
+ create_ps30_parser(ctx);
}
instructions: /* empty */
diff --git a/dlls/d3dx9_36/bytecodewriter.c b/dlls/d3dx9_36/bytecodewriter.c
index abdb78a..665d1db 100644
--- a/dlls/d3dx9_36/bytecodewriter.c
+++ b/dlls/d3dx9_36/bytecodewriter.c
@@ -489,6 +489,19 @@ static void write_constI(const struct bwriter_shader *shader, struct bytecode_bu
}
}
+static void sm_2_opcode(struct bc_writer *This,
+ const struct instruction *instr,
+ DWORD token, struct bytecode_buffer *buffer) {
+ /* From sm 2 onwards instruction length is encoded in the opcode field */
+ int dsts = instr->has_dst ? 1 : 0;
+ token |= instrlen(instr, instr->num_srcs, dsts) << D3DSI_INSTLENGTH_SHIFT;
+ if(instr->comptype)
+ token |= (d3d9_comparetype(instr->comptype) << 16) & (0xf << 16);
+ if(instr->has_predicate)
+ token |= D3DSHADER_INSTRUCTION_PREDICATED;
+ put_dword(buffer,token);
+}
+
static void write_samplers(const struct bwriter_shader *shader, struct bytecode_buffer *buffer) {
DWORD i;
DWORD instr_dcl = D3DSIO_DCL | (2 << D3DSI_INSTLENGTH_SHIFT);
@@ -509,6 +522,246 @@ static void write_samplers(const struct bwriter_shader *shader, struct bytecode_
}
}
+static void sm_3_header(struct bc_writer *This, const struct bwriter_shader *shader, struct bytecode_buffer *buffer) {
+ /* Declare the shader type and version */
+ put_dword(buffer, This->version);
+
+ write_declarations(buffer, TRUE, shader->inputs, shader->num_inputs, D3DSPR_INPUT);
+ write_declarations(buffer, TRUE, shader->outputs, shader->num_outputs, D3DSPR_OUTPUT);
+ write_constF(shader, buffer, TRUE);
+ write_constB(shader, buffer, TRUE);
+ write_constI(shader, buffer, TRUE);
+ write_samplers(shader, buffer);
+ return;
+}
+
+static void sm_3_srcreg(struct bc_writer *This,
+ const struct shader_reg *reg,
+ struct bytecode_buffer *buffer) {
+ DWORD token = (1 << 31); /* Bit 31 of registers is 1 */
+ DWORD d3d9reg;
+
+ d3d9reg = d3d9_register(reg->type);
+ token |= (d3d9reg << D3DSP_REGTYPE_SHIFT) & D3DSP_REGTYPE_MASK;
+ token |= (d3d9reg << D3DSP_REGTYPE_SHIFT2) & D3DSP_REGTYPE_MASK2;
+ token |= reg->regnum & D3DSP_REGNUM_MASK;
+
+ token |= d3d9_swizzle(reg->swizzle) & D3DVS_SWIZZLE_MASK;
+ token |= d3d9_srcmod(reg->srcmod);
+
+ if(reg->rel_reg) {
+ if(reg->type == BWRITERSPR_CONST && This->version == BWRITERPS_VERSION(3, 0)){
+ WARN("c%u[...] is unsupported in ps_3_0\n", reg->regnum);
+ This->state = E_INVALIDARG;
+ return;
+ }
+ if(((reg->rel_reg->type == BWRITERSPR_ADDR && This->version == BWRITERVS_VERSION(3, 0)) ||
+ reg->rel_reg->type == BWRITERSPR_LOOP) &&
+ reg->rel_reg->regnum == 0) {
+ token |= D3DVS_ADDRMODE_RELATIVE & D3DVS_ADDRESSMODE_MASK;
+ } else {
+ WARN("Unsupported relative addressing register\n");
+ This->state = E_INVALIDARG;
+ return;
+ }
+ }
+
+ put_dword(buffer, token);
+
+ /* vs_2_0 and newer write the register containing the index explicitly in the
+ * binary code
+ */
+ if(token & D3DVS_ADDRMODE_RELATIVE) {
+ sm_3_srcreg(This, reg->rel_reg, buffer);
+ }
+}
+
+static void sm_3_dstreg(struct bc_writer *This,
+ const struct shader_reg *reg,
+ struct bytecode_buffer *buffer,
+ DWORD shift, DWORD mod) {
+ DWORD token = (1 << 31); /* Bit 31 of registers is 1 */
+ DWORD d3d9reg;
+
+ if(reg->rel_reg){
+ if(This->version == BWRITERVS_VERSION(3, 0) &&
+ reg->type == BWRITERSPR_OUTPUT) {
+ token |= D3DVS_ADDRMODE_RELATIVE & D3DVS_ADDRESSMODE_MASK;
+ }
+ else {
+ WARN("Relative addressing not supported for this shader type or register type\n");
+ This->state = E_INVALIDARG;
+ return;
+ }
+ }
+
+ d3d9reg = d3d9_register(reg->type);
+ token |= (d3d9reg << D3DSP_REGTYPE_SHIFT) & D3DSP_REGTYPE_MASK;
+ token |= (d3d9reg << D3DSP_REGTYPE_SHIFT2) & D3DSP_REGTYPE_MASK2;
+ token |= reg->regnum & D3DSP_REGNUM_MASK; /* No shift */
+
+ token |= (shift << D3DSP_DSTSHIFT_SHIFT) & D3DSP_DSTSHIFT_MASK;
+ token |= d3d9_dstmod(mod);
+
+ token |= d3d9_writemask(reg->writemask);
+ put_dword(buffer, token);
+
+ /* vs_2_0 and newer write the register containing the index explicitly in the
+ * binary code
+ */
+ if(token & D3DVS_ADDRMODE_RELATIVE) {
+ sm_3_srcreg(This, reg->rel_reg, buffer);
+ }
+}
+
+struct instr_handler_table vs_3_handlers[] = {
+ {BWRITERSIO_ADD, instr_handler},
+ {BWRITERSIO_NOP, instr_handler},
+ {BWRITERSIO_MOV, instr_handler},
+ {BWRITERSIO_SUB, instr_handler},
+ {BWRITERSIO_MAD, instr_handler},
+ {BWRITERSIO_MUL, instr_handler},
+ {BWRITERSIO_RCP, instr_handler},
+ {BWRITERSIO_RSQ, instr_handler},
+ {BWRITERSIO_DP3, instr_handler},
+ {BWRITERSIO_DP4, instr_handler},
+ {BWRITERSIO_MIN, instr_handler},
+ {BWRITERSIO_MAX, instr_handler},
+ {BWRITERSIO_SLT, instr_handler},
+ {BWRITERSIO_SGE, instr_handler},
+ {BWRITERSIO_ABS, instr_handler},
+ {BWRITERSIO_EXP, instr_handler},
+ {BWRITERSIO_LOG, instr_handler},
+ {BWRITERSIO_EXPP, instr_handler},
+ {BWRITERSIO_LOGP, instr_handler},
+ {BWRITERSIO_DST, instr_handler},
+ {BWRITERSIO_LRP, instr_handler},
+ {BWRITERSIO_FRC, instr_handler},
+ {BWRITERSIO_CRS, instr_handler},
+ {BWRITERSIO_SGN, instr_handler},
+ {BWRITERSIO_NRM, instr_handler},
+ {BWRITERSIO_SINCOS, instr_handler},
+ {BWRITERSIO_M4x4, instr_handler},
+ {BWRITERSIO_M4x3, instr_handler},
+ {BWRITERSIO_M3x4, instr_handler},
+ {BWRITERSIO_M3x3, instr_handler},
+ {BWRITERSIO_M3x2, instr_handler},
+ {BWRITERSIO_LIT, instr_handler},
+ {BWRITERSIO_POW, instr_handler},
+ {BWRITERSIO_MOVA, instr_handler},
+
+ {BWRITERSIO_CALL, instr_handler},
+ {BWRITERSIO_CALLNZ, instr_handler},
+ {BWRITERSIO_REP, instr_handler},
+ {BWRITERSIO_ENDREP, instr_handler},
+ {BWRITERSIO_IF, instr_handler},
+ {BWRITERSIO_LABEL, instr_handler},
+ {BWRITERSIO_IFC, instr_handler},
+ {BWRITERSIO_ELSE, instr_handler},
+ {BWRITERSIO_ENDIF, instr_handler},
+ {BWRITERSIO_BREAK, instr_handler},
+ {BWRITERSIO_BREAKC, instr_handler},
+ {BWRITERSIO_LOOP, instr_handler},
+ {BWRITERSIO_RET, instr_handler},
+ {BWRITERSIO_ENDLOOP, instr_handler},
+
+ {BWRITERSIO_SETP, instr_handler},
+ {BWRITERSIO_BREAKP, instr_handler},
+ {BWRITERSIO_TEXLDL, instr_handler},
+
+ {BWRITERSIO_END, NULL},
+};
+
+static struct bytecode_backend vs_3_backend = {
+ sm_3_header,
+ end,
+ sm_3_srcreg,
+ sm_3_dstreg,
+ sm_2_opcode,
+ vs_3_handlers
+};
+
+struct instr_handler_table ps_3_handlers[] = {
+ {BWRITERSIO_ADD, instr_handler},
+ {BWRITERSIO_NOP, instr_handler},
+ {BWRITERSIO_MOV, instr_handler},
+ {BWRITERSIO_SUB, instr_handler},
+ {BWRITERSIO_MAD, instr_handler},
+ {BWRITERSIO_MUL, instr_handler},
+ {BWRITERSIO_RCP, instr_handler},
+ {BWRITERSIO_RSQ, instr_handler},
+ {BWRITERSIO_DP3, instr_handler},
+ {BWRITERSIO_DP4, instr_handler},
+ {BWRITERSIO_MIN, instr_handler},
+ {BWRITERSIO_MAX, instr_handler},
+ {BWRITERSIO_ABS, instr_handler},
+ {BWRITERSIO_EXP, instr_handler},
+ {BWRITERSIO_LOG, instr_handler},
+ {BWRITERSIO_EXPP, instr_handler},
+ {BWRITERSIO_LOGP, instr_handler},
+ {BWRITERSIO_LRP, instr_handler},
+ {BWRITERSIO_FRC, instr_handler},
+ {BWRITERSIO_CRS, instr_handler},
+ {BWRITERSIO_NRM, instr_handler},
+ {BWRITERSIO_SINCOS, instr_handler},
+ {BWRITERSIO_M4x4, instr_handler},
+ {BWRITERSIO_M4x3, instr_handler},
+ {BWRITERSIO_M3x4, instr_handler},
+ {BWRITERSIO_M3x3, instr_handler},
+ {BWRITERSIO_M3x2, instr_handler},
+ {BWRITERSIO_POW, instr_handler},
+ {BWRITERSIO_DP2ADD, instr_handler},
+ {BWRITERSIO_CMP, instr_handler},
+
+ {BWRITERSIO_CALL, instr_handler},
+ {BWRITERSIO_CALLNZ, instr_handler},
+ {BWRITERSIO_REP, instr_handler},
+ {BWRITERSIO_ENDREP, instr_handler},
+ {BWRITERSIO_IF, instr_handler},
+ {BWRITERSIO_LABEL, instr_handler},
+ {BWRITERSIO_IFC, instr_handler},
+ {BWRITERSIO_ELSE, instr_handler},
+ {BWRITERSIO_ENDIF, instr_handler},
+ {BWRITERSIO_BREAK, instr_handler},
+ {BWRITERSIO_BREAKC, instr_handler},
+ {BWRITERSIO_LOOP, instr_handler},
+ {BWRITERSIO_RET, instr_handler},
+ {BWRITERSIO_ENDLOOP, instr_handler},
+
+ {BWRITERSIO_SETP, instr_handler},
+ {BWRITERSIO_BREAKP, instr_handler},
+ {BWRITERSIO_TEXLDL, instr_handler},
+
+ {BWRITERSIO_TEX, instr_handler},
+ {BWRITERSIO_TEX | ( BWRITERSI_TEXLD_PROJECT << BWRITER_OPCODESPECIFICCONTROL_SHIFT ), instr_handler},
+ {BWRITERSIO_TEX | ( BWRITERSI_TEXLD_BIAS << BWRITER_OPCODESPECIFICCONTROL_SHIFT ), instr_handler},
+ {BWRITERSIO_TEXKILL, instr_handler},
+ {BWRITERSIO_DSX, instr_handler},
+ {BWRITERSIO_DSY, instr_handler},
+ {BWRITERSIO_TEXLDD, instr_handler},
+
+ {BWRITERSIO_END, NULL},
+};
+
+static struct bytecode_backend ps_3_backend = {
+ sm_3_header,
+ end,
+ sm_3_srcreg,
+ sm_3_dstreg,
+ sm_2_opcode,
+ ps_3_handlers
+};
+
+static void init_vs30_dx9_writer(struct bc_writer *writer) {
+ TRACE("Creating DirectX9 vertex shader 3.0 writer\n");
+ writer->funcs = &vs_3_backend;
+}
+
+static void init_ps30_dx9_writer(struct bc_writer *writer) {
+ TRACE("Creating DirectX9 pixel shader 3.0 writer\n");
+ writer->funcs = &ps_3_backend;
+}
+
static struct bc_writer *create_writer(DWORD version, DWORD dxversion) {
struct bc_writer *ret = asm_alloc(sizeof(*ret));
@@ -551,7 +804,7 @@ static struct bc_writer *create_writer(DWORD version, DWORD dxversion) {
WARN("Unsupported dxversion for vertex shader 3.0 requested: %u\n", dxversion);
goto fail;
}
- /* TODO: Set the appropriate writer backend */
+ init_vs30_dx9_writer(ret);
break;
case BWRITERPS_VERSION(1, 0):
@@ -611,7 +864,7 @@ static struct bc_writer *create_writer(DWORD version, DWORD dxversion) {
WARN("Unsupported dxversion for pixel shader 3.0 requested: %u\n", dxversion);
goto fail;
}
- /* TODO: Set the appropriate writer backend */
+ init_ps30_dx9_writer(ret);
break;
default:
--
1.6.3.3
More information about the wine-patches
mailing list